diff --git a/.gitignore b/.gitignore index 8c81b746955d..59d88e2e32d9 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ code_reports sdk/storage/azure-storage-blob/tests/settings_real.py sdk/storage/azure-storage-queue/tests/settings_real.py sdk/storage/azure-storage-file/tests/settings_real.py +*.code-workspace diff --git a/doc/dev/mgmt/cheatsheet.md b/doc/dev/mgmt/cheatsheet.md index c91cee0d1b62..14ddc608a88f 100644 --- a/doc/dev/mgmt/cheatsheet.md +++ b/doc/dev/mgmt/cheatsheet.md @@ -1,5 +1,11 @@ # Mgmt helper commands cheat sheet +## Dockerized environment + +Following Dockerfile can be used to build entire enviornment: + +https://github.com/Azure/azure-sdk-for-python/blob/master/tools/Dockerfile + ## Create a venv Windows:
diff --git a/doc/dev/release.md b/doc/dev/release.md index 7b40919ef628..4982c2a2d6e4 100644 --- a/doc/dev/release.md +++ b/doc/dev/release.md @@ -16,13 +16,17 @@ Python packages are uploaded to [PyPI](https://pypi.org/). Once you've uploaded ### Production - Deploy with Azure Dev Ops -Go to this Url: https://aka.ms/pysdkrelease +To avoid "accidental" pushes to our target repositories, [approval](https://docs.microsoft.com/en-us/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) will be requested directly prior to the final PyPI publish. Reference this [wiki page](https://aka.ms/python-approval-groups) and click on `Release to PyPI Approvers` to add yourself to the group for PyPI publishing. + +After taking care of the above, go to this Url: https://aka.ms/pysdkrelease - Click on "Run pipeline" - Change "BuildTargetingString" to the name of your package. Example: azure-mgmt-compute - Change "CandidateForRelease" value to `True` (it should be defaulted to `False`) -Et voila :). Azure Dev Ops will tests one last time the package, dependencies, and the distributions (sdist/wheel) and push to PyPI using the user [azure-sdk](https://pypi.org/user/azure-sdk/). +Et voila :). Azure Dev Ops will tests one last time the package, dependencies, and the distributions (sdist/wheel) and ask for approval prior to pushing to PyPI using the user [azure-sdk](https://pypi.org/user/azure-sdk/). + +Validate artifacts prior to clicking `approve` on the pre-deployment confirmation mail waiting in your inbox. Please allow ~5 minutes for the email to be sent by Azure DevOps ### Production - Deploy manually diff --git a/sdk/core/azure-core/azure/core/pipeline/policies/distributed_tracing.py b/sdk/core/azure-core/azure/core/pipeline/policies/distributed_tracing.py new file mode 100644 index 000000000000..eb86a3207ebb --- /dev/null +++ b/sdk/core/azure-core/azure/core/pipeline/policies/distributed_tracing.py @@ -0,0 +1,106 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +"""Traces network calls using the implementation library from the settings.""" + +from azure.core.pipeline import PipelineRequest, PipelineResponse +from azure.core.tracing.context import tracing_context +from azure.core.tracing.abstract_span import AbstractSpan +from azure.core.tracing.common import set_span_contexts +from azure.core.pipeline.policies import SansIOHTTPPolicy +from azure.core.settings import settings + +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Optional + from azure.core.pipeline.transport import HttpRequest, HttpResponse + + +class DistributedTracingPolicy(SansIOHTTPPolicy): + """The policy to create spans for Azure Calls""" + + def __init__(self): + # type: (str, str, str) -> None + self.parent_span_dict = {} + self._request_id = "x-ms-request-id" + + def set_header(self, request, span): + # type: (PipelineRequest[HttpRequest], Any) -> None + """ + Sets the header information on the span. + """ + headers = span.to_header() + request.http_request.headers.update(headers) + + def on_request(self, request, **kwargs): + # type: (PipelineRequest[HttpRequest], Any) -> None + parent_span = tracing_context.current_span.get() # type: AbstractSpan + + if parent_span is None: + return + + only_propagate = settings.tracing_should_only_propagate() + if only_propagate: + self.set_header(request, parent_span) + return + + path = urlparse(request.http_request.url).path + if not path: + path = "/" + child = parent_span.span(name=path) + child.start() + + set_span_contexts(child) + self.parent_span_dict[child] = parent_span + self.set_header(request, child) + + def end_span(self, request, response=None): + # type: (HttpRequest, Optional[HttpResponse]) -> None + """Ends the span that is tracing the network and updates its status.""" + span = tracing_context.current_span.get() # type: AbstractSpan + only_propagate = settings.tracing_should_only_propagate() + if span and not only_propagate: + span.set_http_attributes(request, response=response) + if response and self._request_id in response.headers: + span.add_attribute(self._request_id, response.headers[self._request_id]) + span.finish() + set_span_contexts(self.parent_span_dict[span]) + + def on_response(self, request, response, **kwargs): + # type: (PipelineRequest[HttpRequest], PipelineResponse[HttpRequest, HttpResponse], Any) -> None + self.end_span(request.http_request, response=response.http_response) + + def on_exception(self, _request, **kwargs): # pylint: disable=unused-argument + # type: (PipelineRequest[HttpRequest], Any) -> bool + self.end_span(_request.http_request) diff --git a/sdk/core/azure-core/azure/core/pipeline/policies/universal.py b/sdk/core/azure-core/azure/core/pipeline/policies/universal.py index f27352045927..90c97524281a 100644 --- a/sdk/core/azure-core/azure/core/pipeline/policies/universal.py +++ b/sdk/core/azure-core/azure/core/pipeline/policies/universal.py @@ -350,7 +350,10 @@ def deserialize_from_http_generics(cls, response): # Try to use content-type from headers if available content_type = None if response.content_type: # type: ignore - content_type = response.content_type[0].strip().lower() # type: ignore + try: + content_type = response.content_type.strip().lower() # type: ignore + except AttributeError: + content_type = response.content_type[0].strip().lower() # type: ignore # Ouch, this server did not declare what it sent... # Let's guess it's JSON... diff --git a/sdk/core/azure-core/azure/core/pipeline/transport/aiohttp.py b/sdk/core/azure-core/azure/core/pipeline/transport/aiohttp.py index c9a425474567..ceec93dd3d84 100644 --- a/sdk/core/azure-core/azure/core/pipeline/transport/aiohttp.py +++ b/sdk/core/azure-core/azure/core/pipeline/transport/aiohttp.py @@ -97,9 +97,7 @@ async def close(self): self._session_owner = False self.session = None - def _build_ssl_config(self, **config): - verify = config.get('connection_verify', self.config.connection.verify) - cert = config.get('connection_cert', self.config.connection.cert) + def _build_ssl_config(self, cert, verify): ssl_ctx = None if cert or verify not in (True, False): @@ -144,7 +142,10 @@ async def send(self, request: HttpRequest, **config: Any) -> Optional[AsyncHttpR await self.open() error = None response = None - config['ssl'] = self._build_ssl_config(**config) + config['ssl'] = self._build_ssl_config( + cert=config.pop('connection_cert', self.config.connection.cert), + verify=config.pop('connection_verify', self.config.connection.verify) + ) try: stream_response = config.pop("stream", False) result = await self.session.request( @@ -152,7 +153,7 @@ async def send(self, request: HttpRequest, **config: Any) -> Optional[AsyncHttpR request.url, headers=request.headers, data=self._get_request_data(request), - timeout=config.get('connection_timeout', self.config.connection.timeout), + timeout=config.pop('connection_timeout', self.config.connection.timeout), allow_redirects=False, **config ) diff --git a/sdk/core/azure-core/azure/core/settings.py b/sdk/core/azure-core/azure/core/settings.py index 566a15b03ab9..e1b4c738cdb8 100644 --- a/sdk/core/azure-core/azure/core/settings.py +++ b/sdk/core/azure-core/azure/core/settings.py @@ -31,7 +31,19 @@ from collections import namedtuple import logging import os -from typing import Any, Union +import six +import sys + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Union + + +from azure.core.tracing import AbstractSpan __all__ = ("settings",) @@ -61,9 +73,9 @@ def convert_bool(value): return value # type: ignore val = value.lower() # type: ignore - if val in ["yes", "1", "on"]: + if val in ["yes", "1", "on", "true", "True"]: return True - if val in ["no", "0", "off"]: + if val in ["no", "0", "off", "false", "False"]: return False raise ValueError("Cannot convert {} to boolean value".format(value)) @@ -102,14 +114,63 @@ def convert_logging(value): val = value.upper() # type: ignore level = _levels.get(val) if not level: - raise ValueError( - "Cannot convert {} to log level, valid values are: {}".format( - value, ", ".join(_levels) - ) - ) + raise ValueError("Cannot convert {} to log level, valid values are: {}".format(value, ", ".join(_levels))) return level +def get_opencensus_span(): + # type: () -> OpenCensusSpan + """Returns the OpenCensusSpan if opencensus is installed else returns None""" + try: + from azure.core.tracing.ext.opencensus_span import OpenCensusSpan + + return OpenCensusSpan + except ImportError: + return None + + +def get_opencensus_span_if_opencensus_is_imported(): + if "opencensus" not in sys.modules: + return None + return get_opencensus_span() + + +_tracing_implementation_dict = {"opencensus": get_opencensus_span} + + +def convert_tracing_impl(value): + # type: (Union[str, AbstractSpan]) -> AbstractSpan + """Convert a string to AbstractSpan + + If a AbstractSpan is passed in, it is returned as-is. Otherwise the function + understands the following strings, ignoring case: + + * "opencensus" + + :param value: the value to convert + :type value: string + :returns: AbstractSpan + :raises ValueError: If conversion to AbstractSpan fails + + """ + if value is None: + return get_opencensus_span_if_opencensus_is_imported() + + wrapper_class = value + if isinstance(value, six.string_types): + value = value.lower() + get_wrapper_class = _tracing_implementation_dict.get(value, lambda: _Unset) + wrapper_class = get_wrapper_class() + if wrapper_class is _Unset: + raise ValueError( + "Cannot convert {} to AbstractSpan, valid values are: {}".format( + value, ", ".join(_tracing_implementation_dict) + ) + ) + + return wrapper_class + + class PrioritizedSetting(object): """Return a value for a global setting according to configuration precedence. @@ -138,9 +199,7 @@ class PrioritizedSetting(object): """ - def __init__( - self, name, env_var=None, system_hook=None, default=_Unset, convert=None - ): + def __init__(self, name, env_var=None, system_hook=None, default=_Unset, convert=None): self._name = name self._env_var = env_var @@ -311,11 +370,7 @@ def defaults(self): """ Return implicit default values for all settings, ignoring environment and system. """ - props = { - k: v.default - for (k, v) in self.__class__.__dict__.items() - if isinstance(v, PrioritizedSetting) - } + props = {k: v.default for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)} return self._config(props) @property @@ -335,11 +390,7 @@ def config(self, **kwargs): settings.config(log_level=logging.DEBUG) """ - props = { - k: v() - for (k, v) in self.__class__.__dict__.items() - if isinstance(v, PrioritizedSetting) - } + props = {k: v() for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)} props.update(kwargs) return self._config(props) @@ -348,17 +399,19 @@ def _config(self, props): # pylint: disable=no-self-use return Config(**props) log_level = PrioritizedSetting( - "log_level", - env_var="AZURE_LOG_LEVEL", - convert=convert_logging, - default=logging.INFO, + "log_level", env_var="AZURE_LOG_LEVEL", convert=convert_logging, default=logging.INFO ) tracing_enabled = PrioritizedSetting( - "tracing_enbled", - env_var="AZURE_TRACING_ENABLED", - convert=convert_bool, - default=False, + "tracing_enbled", env_var="AZURE_TRACING_ENABLED", convert=convert_bool, default=False + ) + + tracing_implementation = PrioritizedSetting( + "tracing_implementation", env_var="AZURE_SDK_TRACING_IMPLEMENTATION", convert=convert_tracing_impl, default=None + ) + + tracing_should_only_propagate = PrioritizedSetting( + "tracing_should_only_propagate", env_var="AZURE_TRACING_ONLY_PROPAGATE", convert=convert_bool, default=False ) diff --git a/sdk/core/azure-core/azure/core/tracing/__init__.py b/sdk/core/azure-core/azure/core/tracing/__init__.py index b2c68383597d..3c4ae3058244 100644 --- a/sdk/core/azure-core/azure/core/tracing/__init__.py +++ b/sdk/core/azure-core/azure/core/tracing/__init__.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ from azure.core.tracing.abstract_span import AbstractSpan -from azure.core.tracing.context import tracing_context - -__all__ = ["tracing_context", "AbstractSpan"] +__all__ = [ + "AbstractSpan", +] diff --git a/sdk/core/azure-core/azure/core/tracing/abstract_span.py b/sdk/core/azure-core/azure/core/tracing/abstract_span.py index 5da1b2123610..9b7ca6753c0a 100644 --- a/sdk/core/azure-core/azure/core/tracing/abstract_span.py +++ b/sdk/core/azure-core/azure/core/tracing/abstract_span.py @@ -2,6 +2,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +"""Protocol that defines what functions wrappers of tracing libraries should implement.""" + try: from typing import TYPE_CHECKING except ImportError: @@ -10,6 +12,8 @@ if TYPE_CHECKING: from typing import Any, Dict, Optional, Union + from azure.core.pipeline.transport import HttpRequest, HttpResponse + try: from typing_extensions import Protocol except ImportError: @@ -19,7 +23,7 @@ class AbstractSpan(Protocol): """Wraps a span from a distributed tracing implementation.""" - def __init__(self, span=None, name=None): + def __init__(self, span=None, name=None): # pylint: disable=super-init-not-called # type: (Optional[Any], Optional[str]) -> None """ If a span is given wraps the span. Else a new span is created. @@ -64,6 +68,18 @@ def add_attribute(self, key, value): """ pass + def set_http_attributes(self, request, response=None): + # type: (HttpRequest, HttpResponse) -> None + """ + Add correct attributes for a http client span. + + :param request: The request made + :type request: HttpRequest + :param response: The response received by the server. Is None if no response received. + :type response: HttpResponse + """ + pass + @property def span_instance(self): # type: () -> Any diff --git a/sdk/core/azure-core/azure/core/tracing/common.py b/sdk/core/azure-core/azure/core/tracing/common.py new file mode 100644 index 000000000000..f6e4716a2eb8 --- /dev/null +++ b/sdk/core/azure-core/azure/core/tracing/common.py @@ -0,0 +1,92 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +"""Common functions shared by both the sync and the async decorators.""" + +from azure.core.tracing.context import tracing_context +from azure.core.tracing.abstract_span import AbstractSpan +from azure.core.settings import settings + + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Optional + + +def set_span_contexts(wrapped_span, span_instance=None): + # type: (AbstractSpan, Optional[AbstractSpan]) -> None + """ + Set the sdk context and the implementation context. `span_instance` will be used to set the implementation context + if passed in else will use `wrapped_span.span_instance`. + + :param wrapped_span: The `AbstractSpan` to set as the sdk context + :type wrapped_span: `azure.core.tracing.abstract_span.AbstractSpan` + :param span_instance: The span to set as the current span for the implementation context + """ + tracing_context.current_span.set(wrapped_span) + impl_wrapper = settings.tracing_implementation() + if wrapped_span is not None: + span_instance = wrapped_span.span_instance + if impl_wrapper is not None: + impl_wrapper.set_current_span(span_instance) + + +def get_parent_span(parent_span): + # type: (Any) -> Tuple(AbstractSpan, AbstractSpan, Any) + """ + Returns the current span so that the function's span will be its child. It will create a new span if there is + no current span in any of the context. + + :param parent_span: The parent_span arg that the user passes into the top level function + :returns: the parent_span of the function to be traced + :rtype: `azure.core.tracing.abstract_span.AbstractSpan` + """ + wrapper_class = settings.tracing_implementation() + if wrapper_class is None: + return None + + orig_wrapped_span = tracing_context.current_span.get() + # parent span is given, get from my context, get from the implementation context or make our own + parent_span = orig_wrapped_span if parent_span is None else wrapper_class(parent_span) + if parent_span is None: + current_span = wrapper_class.get_current_span() + parent_span = ( + wrapper_class(span=current_span) + if current_span + else wrapper_class(name="azure-sdk-for-python-first_parent_span") + ) + + return parent_span + + +def should_use_trace(parent_span): + # type: (AbstractSpan) -> bool + """Given Parent Span Returns whether the function should be traced""" + only_propagate = settings.tracing_should_only_propagate() + return bool(parent_span and not only_propagate) diff --git a/sdk/core/azure-core/azure/core/tracing/context.py b/sdk/core/azure-core/azure/core/tracing/context.py index 4409ce1f6756..a00f88f2d854 100644 --- a/sdk/core/azure-core/azure/core/tracing/context.py +++ b/sdk/core/azure-core/azure/core/tracing/context.py @@ -2,7 +2,10 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +"""The context for the azure.core.tracing. Holds global variables in a thread and async safe way.""" + import threading +from azure.core.settings import settings try: from typing import TYPE_CHECKING @@ -119,7 +122,6 @@ class TracingContext: def __init__(self): # type: () -> None self.current_span = TracingContext._get_context_class("current_span", None) - self.tracing_impl = TracingContext._get_context_class("tracing_impl", None) def with_current_context(self, func): # type: (Callable[[Any], Any]) -> Any @@ -129,7 +131,7 @@ def with_current_context(self, func): :return: The target the pass in instead of the function """ wrapped_span = tracing_context.current_span.get() - wrapper_class = self.tracing_impl.get() + wrapper_class = settings.tracing_implementation() if wrapper_class is not None: current_impl_span = wrapper_class.get_current_span() current_impl_tracer = wrapper_class.get_current_tracer() @@ -140,7 +142,6 @@ def call_with_current_context(*args, **kwargs): wrapper_class.set_current_tracer(current_impl_tracer) current_span = wrapped_span or wrapper_class(current_impl_span) self.current_span.set(current_span) - self.tracing_impl.set(wrapper_class) return func(*args, **kwargs) return call_with_current_context diff --git a/sdk/core/azure-core/azure/core/tracing/decorator.py b/sdk/core/azure-core/azure/core/tracing/decorator.py new file mode 100644 index 000000000000..d033256a799d --- /dev/null +++ b/sdk/core/azure-core/azure/core/tracing/decorator.py @@ -0,0 +1,64 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +"""The decorator to apply if you want the given function traced.""" + +import functools + +import azure.core.tracing.common as common +from azure.core.settings import settings +from azure.core.tracing.context import tracing_context + + +def distributed_trace(func): + # type: (Callable[[Any], Any]) -> Callable[[Any], Any] + @functools.wraps(func) + def wrapper_use_tracer(self, *args, **kwargs): + # type: (Any) -> Any + passed_in_parent = kwargs.pop("parent_span", None) + orig_wrapped_span = tracing_context.current_span.get() + wrapper_class = settings.tracing_implementation() + original_span_instance = None + if wrapper_class is not None: + original_span_instance = wrapper_class.get_current_span() + parent_span = common.get_parent_span(passed_in_parent) + ans = None + if common.should_use_trace(parent_span): + common.set_span_contexts(parent_span) + name = self.__class__.__name__ + "." + func.__name__ + child = parent_span.span(name=name) + child.start() + common.set_span_contexts(child) + ans = func(self, *args, **kwargs) + child.finish() + common.set_span_contexts(parent_span) + if orig_wrapped_span is None and passed_in_parent is None: + parent_span.finish() + common.set_span_contexts(orig_wrapped_span, span_instance=original_span_instance) + else: + ans = func(self, *args, **kwargs) + return ans + + return wrapper_use_tracer diff --git a/sdk/core/azure-core/azure/core/tracing/decorator_async.py b/sdk/core/azure-core/azure/core/tracing/decorator_async.py new file mode 100644 index 000000000000..6e88d235bc9b --- /dev/null +++ b/sdk/core/azure-core/azure/core/tracing/decorator_async.py @@ -0,0 +1,64 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# -------------------------------------------------------------------------- +"""The decorator to apply if you want the given function traced.""" + +import functools + +import azure.core.tracing.common as common +from azure.core.settings import settings +from azure.core.tracing.context import tracing_context + + +def distributed_trace_async(func): + # type: (Callable[[Any], Any]) -> Callable[[Any], Any] + @functools.wraps(func) + async def wrapper_use_tracer(self, *args, **kwargs): + # type: (Any) -> Any + passed_in_parent = kwargs.pop("parent_span", None) + orig_wrapped_span = tracing_context.current_span.get() + wrapper_class = settings.tracing_implementation() + original_span_instance = None + if wrapper_class is not None: + original_span_instance = wrapper_class.get_current_span() + parent_span = common.get_parent_span(passed_in_parent) + ans = None + if common.should_use_trace(parent_span): + common.set_span_contexts(parent_span) + name = self.__class__.__name__ + "." + func.__name__ + child = parent_span.span(name=name) + child.start() + common.set_span_contexts(child) + ans = await func(self, *args, **kwargs) + child.finish() + common.set_span_contexts(parent_span) + if orig_wrapped_span is None and passed_in_parent is None: + parent_span.finish() + common.set_span_contexts(orig_wrapped_span, span_instance=original_span_instance) + else: + ans = await func(self, *args, **kwargs) + return ans + + return wrapper_use_tracer diff --git a/sdk/core/azure-core/azure/core/tracing/ext/opencensus_span.py b/sdk/core/azure-core/azure/core/tracing/ext/opencensus_span.py index 49e3409500d1..d63dc310d390 100644 --- a/sdk/core/azure-core/azure/core/tracing/ext/opencensus_span.py +++ b/sdk/core/azure-core/azure/core/tracing/ext/opencensus_span.py @@ -2,7 +2,10 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from opencensus.trace import tracer as tracer_module, Span, execution_context +"""Implements azure.core.tracing.AbstractSpan to wrap opencensus spans.""" + +from opencensus.trace import Span, execution_context +from opencensus.trace.span import SpanKind from opencensus.trace.link import Link from opencensus.trace.propagation import trace_context_http_header_format @@ -12,7 +15,9 @@ TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Dict, Optional, Union + from typing import Dict, Optional, Union, TypeVar + + from azure.core.pipeline.transport import HttpRequest, HttpResponse class OpenCensusSpan(object): @@ -29,15 +34,15 @@ def __init__(self, span=None, name="span"): :param name: The name of the OpenCensus span to create if a new span is needed :type name: str """ - tracer = self.get_current_tracer() if not span: - current_span = self.get_current_span() - span = tracer.span(name=name) - # The logic is needed until opencensus fixes their bug - # https://github.com/census-instrumentation/opencensus-python/issues/466 - if current_span and span not in current_span.children: - current_span._child_spans.append(span) + tracer = self.get_current_tracer() + span = tracer.span(name=name) # type: Span self._span_instance = span + self._span_component = "component" + self._http_user_agent = "http.user_agent" + self._http_method = "http.method" + self._http_url = "http.url" + self._http_status_code = "http.status_code" @property def span_instance(self): @@ -92,12 +97,34 @@ def add_attribute(self, key, value): """ self.span_instance.add_attribute(key, value) + def set_http_attributes(self, request, response=None): + # type: (HttpRequest, Optional[HttpResponse]) -> None + """ + Add correct attributes for a http client span. + + :param request: The request made + :type request: HttpRequest + :param response: The response received by the server. Is None if no response received. + :type response: HttpResponse + """ + self._span_instance.span_kind = SpanKind.CLIENT + self.span_instance.add_attribute(self._span_component, "http") + self.span_instance.add_attribute(self._http_method, request.method) + self.span_instance.add_attribute(self._http_url, request.url) + user_agent = request.headers.get("User-Agent") + if user_agent: + self.span_instance.add_attribute(self._http_user_agent, user_agent) + if response: + self._span_instance.add_attribute(self._http_status_code, response.status_code) + else: + self._span_instance.add_attribute(self._http_status_code, 504) + @classmethod def link(cls, headers): # type: (Dict[str, str]) -> None """ Given a dictionary, extracts the context and links the context to the current tracer. - + :param headers: A key value pair dictionary :type headers: dict """ diff --git a/sdk/core/azure-core/tests/azure_core_asynctests/test_tracing_decorator_async.py b/sdk/core/azure-core/tests/azure_core_asynctests/test_tracing_decorator_async.py new file mode 100644 index 000000000000..a1d6e19e1b45 --- /dev/null +++ b/sdk/core/azure-core/tests/azure_core_asynctests/test_tracing_decorator_async.py @@ -0,0 +1,150 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""The tests for decorators_async.py""" + +try: + from unittest import mock +except ImportError: + import mock + +import sys +from azure.core import HttpRequest +from azure.core.pipeline import Pipeline, PipelineResponse +from azure.core.pipeline.policies import HTTPPolicy +from azure.core.pipeline.transport import HttpTransport +from azure.core.tracing.context import tracing_context +from azure.core.tracing.decorator import distributed_trace +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.tracing.ext.opencensus_span import OpenCensusSpan +from opencensus.trace import tracer as tracer_module +from opencensus.trace.samplers import AlwaysOnSampler +from tracing_common import ContextHelper, MockExporter +import pytest +import time + + +class MockClient: + @distributed_trace + def __init__(self, policies=None, assert_current_span=False): + time.sleep(0.001) + self.request = HttpRequest("GET", "https://bing.com") + if policies is None: + policies = [] + policies.append(mock.Mock(spec=HTTPPolicy, send=self.verify_request)) + self.policies = policies + self.transport = mock.Mock(spec=HttpTransport) + self.pipeline = Pipeline(self.transport, policies=policies) + + self.expected_response = mock.Mock(spec=PipelineResponse) + self.assert_current_span = assert_current_span + + def verify_request(self, request): + current_span = tracing_context.current_span.get() + if self.assert_current_span: + assert current_span is not None + return self.expected_response + + @distributed_trace_async + async def make_request(self, numb_times, **kwargs): + time.sleep(0.001) + if numb_times < 1: + return None + response = self.pipeline.run(self.request, **kwargs) + await self.get_foo() + await self.make_request(numb_times - 1, **kwargs) + return response + + @distributed_trace_async + async def get_foo(self): + time.sleep(0.001) + return 5 + + +@pytest.mark.asyncio +async def test_with_nothing_imported(): + with ContextHelper(): + opencensus = sys.modules["opencensus"] + del sys.modules["opencensus"] + client = MockClient(assert_current_span=True) + with pytest.raises(AssertionError): + await client.make_request(3) + sys.modules["opencensus"] = opencensus + + +@pytest.mark.asyncio +async def test_with_opencensus_imported_but_not_used(): + with ContextHelper(): + client = MockClient(assert_current_span=True) + await client.make_request(3) + + +@pytest.mark.asyncio +async def test_with_opencencus_used(): + with ContextHelper(): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + parent = trace.start_span(name="OverAll") + client = MockClient(policies=[]) + await client.get_foo(parent_span=parent) + await client.get_foo() + parent.finish() + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 3 + assert parent.children[0].span_data.name == "MockClient.__init__" + assert not parent.children[0].children + assert parent.children[1].span_data.name == "MockClient.get_foo" + assert not parent.children[1].children + + +@pytest.mark.parametrize("value", [None, "opencensus"]) +@pytest.mark.asyncio +async def test_span_with_opencensus_complicated(value): + with ContextHelper(tracer_to_use=value): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.start_span(name="OverAll") as parent: + client = MockClient() + await client.make_request(2) + with trace.span("child") as child: + await client.make_request(2, parent_span=parent) + assert OpenCensusSpan.get_current_span() == child + await client.make_request(2) + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 4 + assert parent.children[0].span_data.name == "MockClient.__init__" + assert parent.children[1].span_data.name == "MockClient.make_request" + assert parent.children[1].children[0].span_data.name == "MockClient.get_foo" + assert parent.children[1].children[1].span_data.name == "MockClient.make_request" + assert parent.children[2].span_data.name == "child" + assert parent.children[2].children[0].span_data.name == "MockClient.make_request" + assert parent.children[3].span_data.name == "MockClient.make_request" + assert parent.children[3].children[0].span_data.name == "MockClient.get_foo" + assert parent.children[3].children[1].span_data.name == "MockClient.make_request" + children = parent.children[1].children + assert len(children) == 2 + + +@pytest.mark.asyncio +async def test_should_only_propagate(): + with ContextHelper(should_only_propagate=True): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.start_span(name="OverAll") as parent: + client = MockClient() + await client.make_request(2) + with trace.span("child") as child: + await client.make_request(2, parent_span=parent) + assert OpenCensusSpan.get_current_span() == child + await client.make_request(2) + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 1 + assert parent.children[0].span_data.name == "child" + assert not parent.children[0].children diff --git a/sdk/core/azure-core/tests/test_settings.py b/sdk/core/azure-core/tests/test_settings.py index a735ff2bfe37..11e9dc48d523 100644 --- a/sdk/core/azure-core/tests/test_settings.py +++ b/sdk/core/azure-core/tests/test_settings.py @@ -25,7 +25,7 @@ # -------------------------------------------------------------------------- import logging import os - +import sys import pytest # module under test @@ -74,7 +74,7 @@ def test_user_set(self): ps = m.PrioritizedSetting("foo") ps.set_value(40) assert ps() == 40 - + def test_user_unset(self): ps = m.PrioritizedSetting("foo", default=2) ps.set_value(40) @@ -101,9 +101,7 @@ def test_precedence(self): assert ps() == 10 # 1. system value - ps = m.PrioritizedSetting( - "foo", env_var="AZURE_FOO", convert=int, default=10, system_hook=lambda: 20 - ) + ps = m.PrioritizedSetting("foo", env_var="AZURE_FOO", convert=int, default=10, system_hook=lambda: 20) assert ps() == 20 # 2. environment variable @@ -137,11 +135,11 @@ class FakeSettings(object): class TestConverters(object): - @pytest.mark.parametrize("value", ["Yes", "YES", "yes", "1", "ON", "on"]) + @pytest.mark.parametrize("value", ["Yes", "YES", "yes", "1", "ON", "on", "true", "True", True]) def test_convert_bool(self, value): assert m.convert_bool(value) - @pytest.mark.parametrize("value", ["No", "NO", "no", "0", "OFF", "off"]) + @pytest.mark.parametrize("value", ["No", "NO", "no", "0", "OFF", "off", "false", "False", False]) def test_convert_bool_false(self, value): assert not m.convert_bool(value) @@ -169,6 +167,18 @@ def test_convert_logging_bad(self): with pytest.raises(ValueError): m.convert_logging("junk") + def test_convert_implementation(self): + if "opencensus" in sys.modules: + del sys.modules["opencensus"] + assert m.convert_tracing_impl(None) is None + assert m.convert_tracing_impl("opencensus") is not None + import opencensus + + assert m.convert_tracing_impl(None) is not None + assert m.convert_tracing_impl("opencensus") is not None + with pytest.raises(ValueError): + m.convert_tracing_impl("does not exist!!") + _standard_settings = ["log_level", "tracing_enabled"] @@ -205,10 +215,22 @@ def test_config(self): def test_defaults(self): val = m.settings.defaults - assert isinstance(val, tuple) - assert val == m.settings.config(log_level=20, tracing_enabled=False) + # assert isinstance(val, tuple) + defaults = m.settings.config( + log_level=20, tracing_enabled=False, tracing_implementation=None, tracing_should_only_propagate=False + ) + assert val.log_level == defaults.log_level + assert val.tracing_enabled == defaults.tracing_enabled + assert val.tracing_implementation == defaults.tracing_implementation + assert val.tracing_should_only_propagate == defaults.tracing_should_only_propagate os.environ["AZURE_LOG_LEVEL"] = "debug" - assert val == m.settings.config(log_level=20, tracing_enabled=False) + defaults = m.settings.config( + log_level=20, tracing_enabled=False, tracing_implementation=None, tracing_should_only_propagate=False + ) + assert val.log_level == defaults.log_level + assert val.tracing_enabled == defaults.tracing_enabled + assert val.tracing_implementation == defaults.tracing_implementation + assert val.tracing_should_only_propagate == defaults.tracing_should_only_propagate del os.environ["AZURE_LOG_LEVEL"] def test_current(self): diff --git a/sdk/core/azure-core/tests/test_tracing_context.py b/sdk/core/azure-core/tests/test_tracing_context.py index 2b8a32762e21..a537d4200179 100644 --- a/sdk/core/azure-core/tests/test_tracing_context.py +++ b/sdk/core/azure-core/tests/test_tracing_context.py @@ -7,45 +7,62 @@ from unittest import mock except ImportError: import mock -from azure.core.tracing import tracing_context + +from azure.core.tracing.context import tracing_context from azure.core.tracing import AbstractSpan +from azure.core.settings import settings +import os + + +class ContextHelper(object): + def __init__(self, environ={}, tracer_to_use=None): + self.orig_sdk_context_span = tracing_context.current_span.get() + self.os_env = mock.patch.dict(os.environ, environ) + self.tracer_to_use = tracer_to_use + + def __enter__(self): + self.orig_sdk_context_span = tracing_context.current_span.get() + settings.tracing_implementation.set_value(self.tracer_to_use) + self.os_env.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + tracing_context.current_span.set(self.orig_sdk_context_span) + settings.tracing_implementation.unset_value() + self.os_env.stop() class TestContext(unittest.TestCase): def test_get_context_class(self): - slot = tracing_context._get_context_class("temp", 1) - assert slot.get() == 1 - slot.set(2) - assert slot.get() == 2 + with ContextHelper(): + slot = tracing_context._get_context_class("temp", 1) + assert slot.get() == 1 + slot.set(2) + assert slot.get() == 2 def test_current_span(self): - assert tracing_context.current_span.get() is None - val = mock.Mock(spec=AbstractSpan) - tracing_context.current_span.set(val) - assert tracing_context.current_span.get() == val - - def test_tracing_impl(self): - assert tracing_context.tracing_impl.get() is None - val = AbstractSpan - tracing_context.tracing_impl.set(val) - assert tracing_context.tracing_impl.get() == val + with ContextHelper(): + assert tracing_context.current_span.get() is None + val = mock.Mock(spec=AbstractSpan) + tracing_context.current_span.set(val) + assert tracing_context.current_span.get() == val def test_with_current_context(self): - from threading import Thread - mock_impl = AbstractSpan - tracing_context.tracing_impl.set(mock_impl) - current_span = mock.Mock(spec=AbstractSpan) - tracing_context.current_span.set(current_span) + with ContextHelper(tracer_to_use=mock.Mock(AbstractSpan)): + from threading import Thread - def work(): - span = tracing_context.current_span.get() - assert span == current_span - setattr(span, "in_worker", True) + current_span = mock.Mock(spec=AbstractSpan) + tracing_context.current_span.set(current_span) + + def work(): + span = tracing_context.current_span.get() + assert span == current_span + setattr(span, "in_worker", True) - thread = Thread(target=tracing_context.with_current_context(work)) - thread.start() - thread.join() + thread = Thread(target=tracing_context.with_current_context(work)) + thread.start() + thread.join() - span = tracing_context.current_span.get() - assert span == current_span - assert getattr(span, "in_worker", False) + span = tracing_context.current_span.get() + assert span == current_span + assert getattr(span, "in_worker", False) diff --git a/sdk/core/azure-core/tests/test_tracing_decorator.py b/sdk/core/azure-core/tests/test_tracing_decorator.py new file mode 100644 index 000000000000..7b27ee392a0f --- /dev/null +++ b/sdk/core/azure-core/tests/test_tracing_decorator.py @@ -0,0 +1,200 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""The tests for decorators.py and common.py""" + +import unittest + +try: + from unittest import mock +except ImportError: + import mock + +import sys +import os +from azure.core import HttpRequest +from azure.core.pipeline import Pipeline, PipelineResponse +from azure.core.pipeline.policies import HTTPPolicy +from azure.core.pipeline.transport import HttpTransport +from azure.core.tracing import common +from azure.core.tracing.context import tracing_context +from azure.core.tracing.decorator import distributed_trace +from azure.core.settings import settings +from azure.core.tracing.ext.opencensus_span import OpenCensusSpan +from opencensus.trace import tracer as tracer_module +from opencensus.trace.samplers import AlwaysOnSampler +from tracing_common import ContextHelper, MockExporter +import time +import pytest + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import List + + +class MockClient: + @distributed_trace + def __init__(self, policies=None, assert_current_span=False): + time.sleep(0.001) + self.request = HttpRequest("GET", "https://bing.com") + if policies is None: + policies = [] + policies.append(mock.Mock(spec=HTTPPolicy, send=self.verify_request)) + self.policies = policies + self.transport = mock.Mock(spec=HttpTransport) + self.pipeline = Pipeline(self.transport, policies=policies) + + self.expected_response = mock.Mock(spec=PipelineResponse) + self.assert_current_span = assert_current_span + + def verify_request(self, request): + current_span = tracing_context.current_span.get() + if self.assert_current_span: + assert current_span is not None + return self.expected_response + + @distributed_trace + def make_request(self, numb_times, **kwargs): + time.sleep(0.001) + if numb_times < 1: + return None + response = self.pipeline.run(self.request, **kwargs) + self.get_foo() + self.make_request(numb_times - 1, **kwargs) + return response + + @distributed_trace + def get_foo(self): + time.sleep(0.001) + return 5 + +class TestCommon(object): + def test_set_span_context(self): + with ContextHelper(environ={"AZURE_SDK_TRACING_IMPLEMENTATION": "opencensus"}): + wrapper = settings.tracing_implementation() + assert wrapper is OpenCensusSpan + assert tracing_context.current_span.get() is None + assert wrapper.get_current_span() is None + parent = OpenCensusSpan() + common.set_span_contexts(parent) + assert parent.span_instance == wrapper.get_current_span() + assert tracing_context.current_span.get() == parent + + def test_get_parent_span(self): + with ContextHelper(): + opencensus = sys.modules["opencensus"] + del sys.modules["opencensus"] + + parent = common.get_parent_span(None) + assert parent is None + + sys.modules["opencensus"] = opencensus + parent = common.get_parent_span(None) + assert parent.span_instance.name == "azure-sdk-for-python-first_parent_span" + + tracer = tracer_module.Tracer(sampler=AlwaysOnSampler()) + parent = common.get_parent_span(None) + assert parent.span_instance.name == "azure-sdk-for-python-first_parent_span" + parent.finish() + + some_span = tracer.start_span(name="some_span") + new_parent = common.get_parent_span(None) + assert new_parent.span_instance.name == "some_span" + some_span.finish() + + should_be_old_parent = common.get_parent_span(parent.span_instance) + assert should_be_old_parent.span_instance == parent.span_instance + + def test_should_use_trace(self): + with ContextHelper(environ={"AZURE_TRACING_ONLY_PROPAGATE": "yes"}): + parent_span = OpenCensusSpan() + assert common.should_use_trace(parent_span) == False + assert common.should_use_trace(None) == False + parent_span = OpenCensusSpan() + assert common.should_use_trace(parent_span) + assert common.should_use_trace(None) == False + + +class TestDecorator(object): + def test_with_nothing_imported(self): + with ContextHelper(): + opencensus = sys.modules["opencensus"] + del sys.modules["opencensus"] + client = MockClient(assert_current_span=True) + with pytest.raises(AssertionError): + client.make_request(3) + sys.modules["opencensus"] = opencensus + + def test_with_opencensus_imported_but_not_used(self): + with ContextHelper(): + client = MockClient(assert_current_span=True) + client.make_request(3) + + def test_with_opencencus_used(self): + with ContextHelper(): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + parent = trace.start_span(name="OverAll") + client = MockClient(policies=[]) + client.get_foo(parent_span=parent) + client.get_foo() + parent.finish() + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 3 + assert parent.children[0].span_data.name == "MockClient.__init__" + assert not parent.children[0].children + assert parent.children[1].span_data.name == "MockClient.get_foo" + assert not parent.children[1].children + + @pytest.mark.parametrize("value", ["opencensus", None]) + def test_span_with_opencensus_complicated(self, value): + with ContextHelper(tracer_to_use=value) as ctx: + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.start_span(name="OverAll") as parent: + client = MockClient() + client.make_request(2) + with trace.span("child") as child: + client.make_request(2, parent_span=parent) + assert OpenCensusSpan.get_current_span() == child + client.make_request(2) + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 4 + assert parent.children[0].span_data.name == "MockClient.__init__" + assert parent.children[1].span_data.name == "MockClient.make_request" + assert parent.children[1].children[0].span_data.name == "MockClient.get_foo" + assert parent.children[1].children[1].span_data.name == "MockClient.make_request" + assert parent.children[2].span_data.name == "child" + assert parent.children[2].children[0].span_data.name == "MockClient.make_request" + assert parent.children[3].span_data.name == "MockClient.make_request" + assert parent.children[3].children[0].span_data.name == "MockClient.get_foo" + assert parent.children[3].children[1].span_data.name == "MockClient.make_request" + children = parent.children[1].children + assert len(children) == 2 + + def test_should_only_propagate(self): + with ContextHelper(should_only_propagate=True): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.start_span(name="OverAll") as parent: + client = MockClient() + client.make_request(2) + with trace.span("child") as child: + client.make_request(2, parent_span=parent) + assert OpenCensusSpan.get_current_span() == child + client.make_request(2) + trace.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 1 + assert parent.children[0].span_data.name == "child" + assert not parent.children[0].children diff --git a/sdk/core/azure-core/tests/test_tracing_implementations.py b/sdk/core/azure-core/tests/test_tracing_implementations.py index 02b0d826b8e1..23df8cff33a6 100644 --- a/sdk/core/azure-core/tests/test_tracing_implementations.py +++ b/sdk/core/azure-core/tests/test_tracing_implementations.py @@ -2,6 +2,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +"""The tests for opencensus_span.py""" + import unittest try: @@ -11,29 +13,14 @@ from azure.core.tracing.ext.opencensus_span import OpenCensusSpan from opencensus.trace import tracer as tracer_module +from opencensus.trace.span import SpanKind from opencensus.trace.samplers import AlwaysOnSampler -from opencensus.ext.azure.trace_exporter import AzureExporter +from opencensus.trace.base_exporter import Exporter +from opencensus.common.utils import timestamp_to_microseconds +from tracing_common import MockExporter, ContextHelper import os -class ContextHelper(object): - def __init__(self, environ={}): - self.orig_tracer = OpenCensusSpan.get_current_tracer() - self.orig_current_span = OpenCensusSpan.get_current_span() - self.os_env = mock.patch.dict(os.environ, environ) - - def __enter__(self): - self.orig_tracer = OpenCensusSpan.get_current_tracer() - self.orig_current_span = OpenCensusSpan.get_current_span() - self.os_env.start() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - OpenCensusSpan.set_current_tracer(self.orig_tracer) - OpenCensusSpan.set_current_span(self.orig_current_span) - self.os_env.stop() - - class TestOpencensusWrapper(unittest.TestCase): def test_span_passed_in(self): with ContextHelper(): @@ -64,8 +51,9 @@ def test_no_span_but_in_trace(self): tracer.finish() def test_span(self): + exporter = MockExporter() with ContextHelper() as ctx: - tracer = tracer_module.Tracer(sampler=AlwaysOnSampler()) + tracer = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) assert OpenCensusSpan.get_current_tracer() is tracer wrapped_class = OpenCensusSpan() assert tracer.current_span() == wrapped_class.span_instance @@ -74,8 +62,11 @@ def test_span(self): assert child.span_instance.name == "span" assert child.span_instance.context_tracer.trace_id == tracer.span_context.trace_id assert child.span_instance.parent_span is wrapped_class.span_instance - assert len(wrapped_class.span_instance.children) == 1 - assert wrapped_class.span_instance.children[0] == child.span_instance + tracer.finish() + exporter.build_tree() + parent = exporter.root + assert len(parent.children) == 1 + assert parent.children[0].span_data.span_id == child.span_instance.span_id def test_start_finish(self): with ContextHelper() as ctx: @@ -120,3 +111,26 @@ def test_add_attribute(self): wrapped_class.add_attribute("test", "test2") assert wrapped_class.span_instance.attributes["test"] == "test2" assert parent.attributes["test"] == "test2" + + def test_set_http_attributes(self): + with ContextHelper(): + trace = tracer_module.Tracer(sampler=AlwaysOnSampler()) + parent = trace.start_span() + wrapped_class = OpenCensusSpan(span=parent) + request = mock.Mock() + setattr(request, "method", "GET") + setattr(request, "url", "some url") + response = mock.Mock() + setattr(request, "headers", {}) + setattr(response, "status_code", 200) + wrapped_class.set_http_attributes(request) + assert wrapped_class.span_instance.span_kind == SpanKind.CLIENT + assert wrapped_class.span_instance.attributes.get("http.method") == request.method + assert wrapped_class.span_instance.attributes.get("component") == "http" + assert wrapped_class.span_instance.attributes.get("http.url") == request.url + assert wrapped_class.span_instance.attributes.get("http.status_code") == 504 + assert wrapped_class.span_instance.attributes.get("http.user_agent") is None + request.headers["User-Agent"] = "some user agent" + wrapped_class.set_http_attributes(request, response) + assert wrapped_class.span_instance.attributes.get("http.status_code") == response.status_code + assert wrapped_class.span_instance.attributes.get("http.user_agent") == request.headers.get("User-Agent") diff --git a/sdk/core/azure-core/tests/test_tracing_policy.py b/sdk/core/azure-core/tests/test_tracing_policy.py new file mode 100644 index 000000000000..420b6c998b60 --- /dev/null +++ b/sdk/core/azure-core/tests/test_tracing_policy.py @@ -0,0 +1,122 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Tests for the distributed tracing policy.""" + +from azure.core.tracing.context import tracing_context +from azure.core.pipeline import PipelineResponse, PipelineRequest, PipelineContext +from azure.core.pipeline.policies.distributed_tracing import DistributedTracingPolicy +from azure.core.pipeline.policies.universal import UserAgentPolicy +from azure.core.pipeline.transport import HttpRequest, HttpResponse +from opencensus.trace import tracer as tracer_module +from opencensus.trace.samplers import AlwaysOnSampler +from azure.core.tracing.ext.opencensus_span import OpenCensusSpan +from tracing_common import ContextHelper, MockExporter +import time + +def test_distributed_tracing_policy_solo(): + """Test policy with no other policy and happy path""" + with ContextHelper(): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.span("parent"): + tracing_context.current_span.set(OpenCensusSpan(trace.current_span())) + policy = DistributedTracingPolicy() + + request = HttpRequest("GET", "http://127.0.0.1/temp?query=query") + + pipeline_request = PipelineRequest(request, PipelineContext(None)) + policy.on_request(pipeline_request) + + response = HttpResponse(request, None) + response.headers = request.headers + response.status_code = 202 + response.headers["x-ms-request-id"] = "some request id" + + ctx = trace.span_context + header = trace.propagator.to_headers(ctx) + assert request.headers.get("traceparent") == header.get("traceparent") + + policy.on_response(pipeline_request, PipelineResponse(request, response, PipelineContext(None))) + time.sleep(0.001) + policy.on_request(pipeline_request) + policy.on_exception(pipeline_request) + + trace.finish() + exporter.build_tree() + parent = exporter.root + network_span = parent.children[0] + assert network_span.span_data.name == "/temp" + assert network_span.span_data.attributes.get("http.method") == "GET" + assert network_span.span_data.attributes.get("component") == "http" + assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1/temp?query=query" + assert network_span.span_data.attributes.get("http.user_agent") is None + assert network_span.span_data.attributes.get("x-ms-request-id") == "some request id" + assert network_span.span_data.attributes.get("http.status_code") == 202 + + network_span = parent.children[1] + assert network_span.span_data.name == "/temp" + assert network_span.span_data.attributes.get("http.method") == "GET" + assert network_span.span_data.attributes.get("component") == "http" + assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1/temp?query=query" + assert network_span.span_data.attributes.get("http.user_agent") is None + assert network_span.span_data.attributes.get("x-ms-request-id") == None + assert network_span.span_data.attributes.get("http.status_code") == 504 + + +def test_distributed_tracing_policy_with_user_agent(): + """Test policy working with user agent.""" + with ContextHelper(environ={"AZURE_HTTP_USER_AGENT": "mytools"}): + exporter = MockExporter() + trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) + with trace.span("parent"): + tracing_context.current_span.set(OpenCensusSpan(trace.current_span())) + policy = DistributedTracingPolicy() + + request = HttpRequest("GET", "http://127.0.0.1") + + pipeline_request = PipelineRequest(request, PipelineContext(None)) + + user_agent = UserAgentPolicy() + user_agent.on_request(pipeline_request) + policy.on_request(pipeline_request) + + response = HttpResponse(request, None) + response.headers = request.headers + response.status_code = 202 + response.headers["x-ms-request-id"] = "some request id" + pipeline_response = PipelineResponse(request, response, PipelineContext(None)) + + ctx = trace.span_context + header = trace.propagator.to_headers(ctx) + assert request.headers.get("traceparent") == header.get("traceparent") + + policy.on_response(pipeline_request, pipeline_response) + + time.sleep(0.001) + policy.on_request(pipeline_request) + policy.on_exception(pipeline_request) + + user_agent.on_response(pipeline_request, pipeline_response) + + trace.finish() + exporter.build_tree() + parent = exporter.root + network_span = parent.children[0] + assert network_span.span_data.name == "/" + assert network_span.span_data.attributes.get("http.method") == "GET" + assert network_span.span_data.attributes.get("component") == "http" + assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1" + assert network_span.span_data.attributes.get("http.user_agent").endswith("mytools") + assert network_span.span_data.attributes.get("x-ms-request-id") == "some request id" + assert network_span.span_data.attributes.get("http.status_code") == 202 + + network_span = parent.children[1] + assert network_span.span_data.name == "/" + assert network_span.span_data.attributes.get("http.method") == "GET" + assert network_span.span_data.attributes.get("component") == "http" + assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1" + assert network_span.span_data.attributes.get("http.user_agent").endswith("mytools") + assert network_span.span_data.attributes.get("x-ms-request-id") is None + assert network_span.span_data.attributes.get("http.status_code") == 504 diff --git a/sdk/core/azure-core/tests/tracing_common.py b/sdk/core/azure-core/tests/tracing_common.py new file mode 100644 index 000000000000..caa8605762df --- /dev/null +++ b/sdk/core/azure-core/tests/tracing_common.py @@ -0,0 +1,87 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Code shared between the async and the sync test_decorator files.""" + +import sys +import os +from azure.core import HttpRequest +from azure.core.pipeline import Pipeline, PipelineResponse +from azure.core.pipeline.policies import HTTPPolicy +from azure.core.pipeline.transport import HttpTransport +from azure.core.tracing import common +from azure.core.tracing.context import tracing_context +from azure.core.settings import settings +from azure.core.tracing.ext.opencensus_span import OpenCensusSpan +from opencensus.trace import tracer as tracer_module +from opencensus.trace.span_data import SpanData +from opencensus.trace.samplers import AlwaysOnSampler +from opencensus.trace.base_exporter import Exporter +from collections import defaultdict + +try: + from unittest import mock +except ImportError: + import mock + + +class ContextHelper(object): + def __init__(self, environ={}, tracer_to_use=None, should_only_propagate=None): + self.orig_tracer = OpenCensusSpan.get_current_tracer() + self.orig_current_span = OpenCensusSpan.get_current_span() + self.orig_sdk_context_span = tracing_context.current_span.get() + self.os_env = mock.patch.dict(os.environ, environ) + self.tracer_to_use = tracer_to_use + self.should_only_propagate = should_only_propagate + + def __enter__(self): + self.orig_tracer = OpenCensusSpan.get_current_tracer() + self.orig_current_span = OpenCensusSpan.get_current_span() + self.orig_sdk_context_span = tracing_context.current_span.get() + if self.tracer_to_use is not None: + settings.tracing_implementation.set_value(self.tracer_to_use) + if self.should_only_propagate is not None: + settings.tracing_should_only_propagate.set_value(self.should_only_propagate) + self.os_env.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + OpenCensusSpan.set_current_tracer(self.orig_tracer) + OpenCensusSpan.set_current_span(self.orig_current_span) + tracing_context.current_span.set(self.orig_sdk_context_span) + settings.tracing_implementation.unset_value() + settings.tracing_should_only_propagate.unset_value() + self.os_env.stop() + + +class Node: + def __init__(self, span_data): + self.span_data = span_data # type: SpanData + self.parent = None + self.children = [] + + +class MockExporter(Exporter): + def __init__(self): + self.root = None + self._all_nodes = [] + self.parent_dict = defaultdict(list) + + def export(self, span_datas): + # type: (List[SpanData]) -> None + sp = span_datas[0] # type: SpanData + node = Node(sp) + if not node.span_data.parent_span_id: + self.root = node + parent_span_id = node.span_data.parent_span_id + self.parent_dict[parent_span_id].append(node) + self._all_nodes.append(node) + + def build_tree(self): + for node in self._all_nodes: + if node.span_data.span_id in self.parent_dict: + node.children = sorted( + self.parent_dict[node.span_data.span_id], + key=lambda x: x.span_data.start_time, + ) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py new file mode 100644 index 000000000000..9ea29a25784d --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py @@ -0,0 +1,6 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from .msal_credentials import ConfidentialClientCredential +from .msal_transport_adapter import MsalTransportResponse, MsalTransportAdapter diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py new file mode 100644 index 000000000000..9bf44cbb3219 --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -0,0 +1,88 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Credentials wrapping MSAL applications and delegating token acquisition and caching to them. +This entails monkeypatching MSAL's OAuth client with an adapter substituting an azure-core pipeline for Requests. +""" + +import time + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +try: + from unittest import mock +except ImportError: # python < 3.3 + import mock # type: ignore + +if TYPE_CHECKING: + # pylint:disable=unused-import + from typing import Any, Mapping, Optional, Union + +from azure.core.credentials import AccessToken +from azure.core.exceptions import ClientAuthenticationError +import msal + +from .msal_transport_adapter import MsalTransportAdapter + + +class MsalCredential(object): + """Base class for credentials wrapping MSAL applications""" + + def __init__(self, client_id, authority, app_class, client_credential=None, **kwargs): + # type: (str, str, msal.ClientApplication, Optional[Union[str, Mapping[str, str]]], Any) -> None + self._authority = authority + self._client_credential = client_credential + self._client_id = client_id + + self._adapter = kwargs.pop("msal_adapter", None) or MsalTransportAdapter(**kwargs) + + # postpone creating the wrapped application because its initializer uses the network + self._app_class = app_class + self._msal_app = None # type: Optional[msal.ClientApplication] + + @property + def _app(self): + # type: () -> msal.ClientApplication + """The wrapped MSAL application""" + + if not self._msal_app: + # MSAL application initializers use msal.authority to send AAD tenant discovery requests + with mock.patch("msal.authority.requests", self._adapter): + app = self._app_class( + client_id=self._client_id, client_credential=self._client_credential, authority=self._authority + ) + + # monkeypatch the app to replace requests.Session with MsalTransportAdapter + app.client.session = self._adapter + self._msal_app = app + + return self._msal_app + + +class ConfidentialClientCredential(MsalCredential): + """Wraps an MSAL ConfidentialClientApplication with the TokenCredential API""" + + def __init__(self, **kwargs): + # type: (Any) -> None + super(ConfidentialClientCredential, self).__init__(app_class=msal.ConfidentialClientApplication, **kwargs) + + def get_token(self, *scopes): + # type: (str) -> AccessToken + + # MSAL requires scopes be a list + scopes = list(scopes) # type: ignore + now = int(time.time()) + + # First try to get a cached access token or if a refresh token is cached, redeem it for an access token. + # Failing that, acquire a new token. + app = self._app # type: msal.ConfidentialClientApplication + result = app.acquire_token_silent(scopes, account=None) or app.acquire_token_for_client(scopes) + + if "access_token" not in result: + raise ClientAuthenticationError(message="authentication failed: {}".format(result.get("error_description"))) + + return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py new file mode 100644 index 000000000000..1a19beaf1c3d --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py @@ -0,0 +1,88 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Adapter to substitute an azure-core pipeline for Requests in MSAL application token acquisition methods.""" + +import json + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint:disable=unused-import + from typing import Any, Dict, Mapping, Optional + from azure.core.pipeline import PipelineResponse + +from azure.core.configuration import Configuration +from azure.core.exceptions import ClientAuthenticationError +from azure.core.pipeline import Pipeline +from azure.core.pipeline.policies import ContentDecodePolicy, NetworkTraceLoggingPolicy, RetryPolicy +from azure.core.pipeline.transport import HttpRequest, RequestsTransport + + +class MsalTransportResponse: + """Wraps an azure-core PipelineResponse with the shape of requests.Response""" + + def __init__(self, pipeline_response): + # type: (PipelineResponse) -> None + self._response = pipeline_response.http_response + self.status_code = self._response.status_code + self.text = self._response.text() + + def json(self, **kwargs): + # type: (Any) -> Mapping[str, Any] + return json.loads(self.text, **kwargs) + + def raise_for_status(self): + # type: () -> None + raise ClientAuthenticationError("authentication failed", self._response) + + +class MsalTransportAdapter(object): + """Wraps an azure-core pipeline with the shape of requests.Session""" + + def __init__(self, **kwargs): + # type: (Any) -> None + super(MsalTransportAdapter, self).__init__() + self._pipeline = self._build_pipeline(**kwargs) + + @staticmethod + def create_config(**kwargs): + # type: (Any) -> Configuration + config = Configuration(**kwargs) + config.logging_policy = NetworkTraceLoggingPolicy(**kwargs) + config.retry_policy = RetryPolicy(**kwargs) + return config + + def _build_pipeline(self, config=None, policies=None, transport=None, **kwargs): + config = config or self.create_config(**kwargs) + policies = policies or [ContentDecodePolicy(), config.retry_policy, config.logging_policy] + if not transport: + transport = RequestsTransport(configuration=config) + return Pipeline(transport=transport, policies=policies) + + def get(self, url, headers=None, params=None, timeout=None, verify=None, **kwargs): + # type: (str, Optional[Mapping[str, str]], Optional[Dict[str, str]], float, bool, Any) -> MsalTransportResponse + request = HttpRequest("GET", url, headers=headers) + if params: + request.format_parameters(params) + response = self._pipeline.run( + request, stream=False, connection_timeout=timeout, connection_verify=verify, **kwargs + ) + return MsalTransportResponse(response) + + def post(self, url, data=None, headers=None, params=None, timeout=None, verify=None, **kwargs): + # type: (str, Optional[Mapping[str, str]], Optional[Mapping[str, str]], Optional[Dict[str, str]], float, bool, Any) -> MsalTransportResponse + request = HttpRequest("POST", url, headers=headers) + if params: + request.format_parameters(params) + if data: + request.headers["Content-Type"] = "application/x-www-form-urlencoded" + request.set_formdata_body(data) + response = self._pipeline.run( + request, stream=False, connection_timeout=timeout, connection_verify=verify, **kwargs + ) + return MsalTransportResponse(response) diff --git a/sdk/identity/azure-identity/azure/identity/_internal.py b/sdk/identity/azure-identity/azure/identity/_managed_identity.py similarity index 100% rename from sdk/identity/azure-identity/azure/identity/_internal.py rename to sdk/identity/azure-identity/azure/identity/_managed_identity.py diff --git a/sdk/identity/azure-identity/azure/identity/aio/_internal.py b/sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py similarity index 99% rename from sdk/identity/azure-identity/azure/identity/aio/_internal.py rename to sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py index 4f502e95ed17..ec698b52c98b 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_internal.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py @@ -12,7 +12,7 @@ from ._authn_client import AsyncAuthnClient from ..constants import Endpoints, EnvironmentVariables -from .._internal import _ManagedIdentityBase +from .._managed_identity import _ManagedIdentityBase class _AsyncManagedIdentityBase(_ManagedIdentityBase): diff --git a/sdk/identity/azure-identity/azure/identity/aio/credentials.py b/sdk/identity/azure-identity/azure/identity/aio/credentials.py index 7f9f846f9ea1..c43dc917e518 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/aio/credentials.py @@ -14,7 +14,7 @@ from azure.core.pipeline.policies import ContentDecodePolicy, HeadersPolicy, NetworkTraceLoggingPolicy, AsyncRetryPolicy from ._authn_client import AsyncAuthnClient -from ._internal import ImdsCredential, MsiCredential +from ._managed_identity import ImdsCredential, MsiCredential from .._base import ClientSecretCredentialBase, CertificateCredentialBase from ..constants import Endpoints, EnvironmentVariables from ..credentials import ChainedTokenCredential diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index 6a172995d563..d53edf8e2c62 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -14,7 +14,7 @@ from ._authn_client import AuthnClient from ._base import ClientSecretCredentialBase, CertificateCredentialBase -from ._internal import ImdsCredential, MsiCredential +from ._managed_identity import ImdsCredential, MsiCredential from .constants import Endpoints, EnvironmentVariables try: diff --git a/sdk/identity/azure-identity/tests/test_identity.py b/sdk/identity/azure-identity/tests/test_identity.py index 72d9a31dbf0a..c9756ad3b344 100644 --- a/sdk/identity/azure-identity/tests/test_identity.py +++ b/sdk/identity/azure-identity/tests/test_identity.py @@ -22,7 +22,7 @@ ManagedIdentityCredential, ChainedTokenCredential, ) -from azure.identity._internal import ImdsCredential +from azure.identity._managed_identity import ImdsCredential from azure.identity.constants import EnvironmentVariables from helpers import mock_response, Request, validating_transport diff --git a/sdk/identity/azure-identity/tests/test_identity_async.py b/sdk/identity/azure-identity/tests/test_identity_async.py index 78230c94bb09..ba203cd2eb59 100644 --- a/sdk/identity/azure-identity/tests/test_identity_async.py +++ b/sdk/identity/azure-identity/tests/test_identity_async.py @@ -19,7 +19,7 @@ EnvironmentCredential, ManagedIdentityCredential, ) -from azure.identity.aio._internal import ImdsCredential +from azure.identity.aio._managed_identity import ImdsCredential from azure.identity.constants import EnvironmentVariables from helpers import mock_response, Request, async_validating_transport diff --git a/sdk/identity/azure-identity/tests/test_live.py b/sdk/identity/azure-identity/tests/test_live.py index 891524a48929..ddff3d83fa3a 100644 --- a/sdk/identity/azure-identity/tests/test_live.py +++ b/sdk/identity/azure-identity/tests/test_live.py @@ -2,16 +2,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -import os - -try: - from unittest import mock -except ImportError: # python < 3.3 - import mock # type: ignore - from azure.identity import DefaultAzureCredential, CertificateCredential, ClientSecretCredential -from azure.identity.constants import EnvironmentVariables -import pytest +from azure.identity._internal import ConfidentialClientCredential ARM_SCOPE = "https://management.azure.com/.default" @@ -46,3 +38,15 @@ def test_default_credential(live_identity_settings): assert token assert token.token assert token.expires_on + + +def test_confidential_client_credential(live_identity_settings): + credential = ConfidentialClientCredential( + client_id=live_identity_settings["client_id"], + client_credential=live_identity_settings["client_secret"], + authority="https://login.microsoftonline.com/" + live_identity_settings["tenant_id"], + ) + token = credential.get_token(ARM_SCOPE) + assert token + assert token.token + assert token.expires_on diff --git a/sdk/keyvault/azure-keyvault-keys/README.md b/sdk/keyvault/azure-keyvault-keys/README.md index 92d6d0132d64..59463e7efc55 100644 --- a/sdk/keyvault/azure-keyvault-keys/README.md +++ b/sdk/keyvault/azure-keyvault-keys/README.md @@ -106,7 +106,7 @@ key = key_client.create_key("key-name", "RSA-HSM") rsa_key = key_client.create_rsa_key("rsa-key-name", hsm=False, size=2048) # Create an EC key with curve specification and using HSM -ec_key = key_client.create_key("ec-key-name", hsm=True, curve="P-256") +ec_key = key_client.create_ec_key("ec-key-name", hsm=True, curve="P-256") print(key.name) print(key.key_material.kty) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/__init__.py index ab1a4e0c3f8e..c00fd1461b4b 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/__init__.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/__init__.py @@ -3,6 +3,5 @@ # Licensed under the MIT License. # ------------------------------------- from ._client import KeyClient -from ._models import Key, KeyBase, DeletedKey, KeyOperationResult __all__ = ["KeyClient"] diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_client.py index 996683569bb9..90aeb33200d9 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_client.py @@ -2,16 +2,23 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from typing import Any, Dict, Generator, Mapping, Optional, List from datetime import datetime +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Dict, Generator, Mapping, Optional + from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError -from ._internal import _KeyVaultClientBase +from ._shared import KeyVaultClientBase from ._models import Key, KeyBase, DeletedKey, KeyOperationResult -class KeyClient(_KeyVaultClientBase): +class KeyClient(KeyVaultClientBase): """KeyClient is a high-level interface for managing a vault's keys. Example: diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_models.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_models.py index e8609bdd32a7..19ceda29966a 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_models.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_models.py @@ -2,12 +2,19 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------- - import datetime -from typing import Any, Dict, Mapping, Optional + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Dict, Generator, Mapping, Optional + from collections import namedtuple -from ._internal import _parse_vault_id -from ._generated.v7_0 import models +from ._shared import parse_vault_id +from ._shared._generated.v7_0 import models KeyOperationResult = namedtuple("KeyOperationResult", ["id", "value"]) @@ -19,7 +26,7 @@ def __init__(self, attributes, vault_id, **kwargs): # type: (models.KeyAttributes, str, Mapping[str, Any]) -> None self._attributes = attributes self._id = vault_id - self._vault_id = _parse_vault_id(vault_id) + self._vault_id = parse_vault_id(vault_id) self._managed = kwargs.get("managed", None) self._tags = kwargs.get("tags", None) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py new file mode 100644 index 000000000000..beb24c202495 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/__init__.py @@ -0,0 +1,56 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from collections import namedtuple + +try: + import urllib.parse as parse +except ImportError: + # pylint:disable=import-error + import urlparse as parse # type: ignore + +from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .client_base import KeyVaultClientBase +from .http_challenge import HttpChallenge +from . import http_challenge_cache as HttpChallengeCache + +__all__ = [ + "ChallengeAuthPolicy", + "ChallengeAuthPolicyBase", + "HttpChallenge", + "HttpChallengeCache", + "KeyVaultClientBase", +] + +_VaultId = namedtuple("VaultId", ["vault_url", "collection", "name", "version"]) + + +def parse_vault_id(url): + try: + parsed_uri = parse.urlparse(url) + except Exception: # pylint: disable=broad-except + raise ValueError("'{}' is not not a valid url".format(url)) + if not (parsed_uri.scheme and parsed_uri.hostname): + raise ValueError("'{}' is not not a valid url".format(url)) + + path = list(filter(None, parsed_uri.path.split("/"))) + + if len(path) < 2 or len(path) > 3: + raise ValueError("'{}' is not not a valid vault url".format(url)) + + return _VaultId( + vault_url="{}://{}".format(parsed_uri.scheme, parsed_uri.hostname), + collection=path[0], + name=path[1], + version=path[2] if len(path) == 3 else None, + ) + + +try: + from .async_challenge_auth_policy import AsyncChallengeAuthPolicy + from .async_client_base import AsyncKeyVaultClientBase, AsyncPagingAdapter + + __all__.extend(["AsyncChallengeAuthPolicy", "AsyncKeyVaultClientBase", "AsyncPagingAdapter"]) +except (SyntaxError, ImportError): + pass diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/key_vault_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/key_vault_client.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/_configuration.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/_configuration.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/_configuration.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/_configuration.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/_key_vault_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/_key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/_key_vault_client.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/_key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/_configuration_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/_configuration_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/_configuration_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/_configuration_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/_key_vault_client_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/_key_vault_client_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/_key_vault_client_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/_key_vault_client_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/operations_async/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/operations_async/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/operations_async/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/operations_async/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_key_vault_client_enums.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_key_vault_client_enums.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_key_vault_client_enums.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_key_vault_client_enums.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_models.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_models.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_models.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_models_py3.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_models_py3.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_models_py3.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_models_py3.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_paged_models.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_paged_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/models/_paged_models.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/models/_paged_models.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/operations/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/operations/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/operations/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/operations/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/operations/_key_vault_client_operations.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/operations/_key_vault_client_operations.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/operations/_key_vault_client_operations.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/operations/_key_vault_client_operations.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/version.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/version.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v2016_10_01/version.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v2016_10_01/version.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/_configuration.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/_configuration.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/_configuration.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/_configuration.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/_key_vault_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/_key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/_key_vault_client.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/_key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/_configuration_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/_configuration_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/_configuration_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/_configuration_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/_key_vault_client_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/_key_vault_client_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/_key_vault_client_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/_key_vault_client_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/operations_async/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/operations_async/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/operations_async/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/operations_async/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_key_vault_client_enums.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_key_vault_client_enums.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_key_vault_client_enums.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_key_vault_client_enums.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_models.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_models.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_models.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_models_py3.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_models_py3.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_models_py3.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_models_py3.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_paged_models.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_paged_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/models/_paged_models.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/models/_paged_models.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/operations/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/operations/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/operations/__init__.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/operations/__init__.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/operations/_key_vault_client_operations.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/operations/_key_vault_client_operations.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/operations/_key_vault_client_operations.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/operations/_key_vault_client_operations.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/version.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/version.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_generated/v7_0/version.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/_generated/v7_0/version.py diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py new file mode 100644 index 000000000000..d07718d9c5e6 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_challenge_auth_policy.py @@ -0,0 +1,57 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.transport import HttpRequest, HttpResponse + +from . import ChallengeAuthPolicyBase, HttpChallenge, HttpChallengeCache + + +class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): + """policy for handling HTTP authentication challenges""" + + async def send(self, request: PipelineRequest) -> HttpResponse: + challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) + if not challenge: + # provoke a challenge with an unauthorized, bodiless request + no_body = HttpRequest( + request.http_request.method, request.http_request.url, headers=request.http_request.headers + ) + if request.http_request.body: + # no_body was created with request's headers -> if request has a body, no_body's content-length is wrong + no_body.headers["Content-Length"] = "0" + + challenger = await self.next.send(PipelineRequest(http_request=no_body, context=request.context)) + try: + challenge = self._update_challenge(request, challenger) + except ValueError: + # didn't receive the expected challenge -> nothing more this policy can do + return challenger + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + if response.http_response.status_code == 401: + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = self._update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response + + async def _handle_challenge(self, request: PipelineRequest, challenge: HttpChallenge) -> None: + """authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_resource() + if not scope.endswith("/.default"): + scope += "/.default" + + access_token = await self._credential.get_token(scope) + self._update_headers(request.http_request.headers, access_token.token) diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_internal.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_client_base.py similarity index 93% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_internal.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_client_base.py index d1f74f7e449e..7424b5162ea3 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_internal.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/async_client_base.py @@ -6,12 +6,11 @@ from azure.core.async_paging import AsyncPagedMixin from azure.core.configuration import Configuration from azure.core.pipeline import AsyncPipeline -from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from azure.core.pipeline.transport import AsyncioRequestsTransport, HttpTransport from msrest.serialization import Model -from .._generated import KeyVaultClient -from .._internal import KEY_VAULT_SCOPE +from ._generated import KeyVaultClient +from . import AsyncChallengeAuthPolicy if TYPE_CHECKING: @@ -41,7 +40,7 @@ async def __anext__(self) -> Any: # TODO: expected type Model got Coroutine instead? -class _AsyncKeyVaultClientBase: +class AsyncKeyVaultClientBase: """ :param credential: A credential or credential provider which can be used to authenticate to the vault, a ValueError will be raised if the entity is not provided @@ -58,7 +57,7 @@ def create_config( if api_version is None: api_version = KeyVaultClient.DEFAULT_API_VERSION config = KeyVaultClient.get_configuration_class(api_version, aio=True)(credential, **kwargs) - config.authentication_policy = AsyncBearerTokenCredentialPolicy(credential, KEY_VAULT_SCOPE) + config.authentication_policy = AsyncChallengeAuthPolicy(credential) return config def __init__( diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py new file mode 100644 index 000000000000..204a59ddc05e --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/challenge_auth_policy.py @@ -0,0 +1,91 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint:disable=unused-import + from azure.core.pipeline.transport import HttpResponse + +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import HTTPPolicy +from azure.core.pipeline.policies.authentication import _BearerTokenCredentialPolicyBase +from azure.core.pipeline.transport import HttpRequest + +from .http_challenge import HttpChallenge +from . import http_challenge_cache as ChallengeCache + + +class ChallengeAuthPolicyBase(_BearerTokenCredentialPolicyBase): + """Sans I/O base for challenge authentication policies""" + + def __init__(self, credential, **kwargs): + super(ChallengeAuthPolicyBase, self).__init__(credential, **kwargs) + + @staticmethod + def _update_challenge(request, challenger): + # type: (HttpRequest, HttpResponse) -> HttpChallenge + """parse challenge from challenger, cache it, return it""" + + challenge = HttpChallenge( + request.http_request.url, + challenger.http_response.headers.get("WWW-Authenticate"), + response_headers=challenger.http_response.headers, + ) + ChallengeCache.set_challenge_for_url(request.http_request.url, challenge) + return challenge + + +class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): + """policy for handling HTTP authentication challenges""" + + def send(self, request): + # type: (PipelineRequest) -> HttpResponse + + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if not challenge: + # provoke a challenge with an unauthorized, bodiless request + no_body = HttpRequest( + request.http_request.method, request.http_request.url, headers=request.http_request.headers + ) + if request.http_request.body: + # no_body was created with request's headers -> if request has a body, no_body's content-length is wrong + no_body.headers["Content-Length"] = "0" + + challenger = self.next.send(PipelineRequest(http_request=no_body, context=request.context)) + try: + challenge = self._update_challenge(request, challenger) + except ValueError: + # didn't receive the expected challenge -> nothing more this policy can do + return challenger + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + if response.http_response.status_code == 401: + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = self._update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_resource() + if not scope.endswith("/.default"): + scope += "/.default" + + access_token = self._credential.get_token(scope) + self._update_headers(request.http_request.headers, access_token.token) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_internal.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/client_base.py similarity index 74% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_internal.py rename to sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/client_base.py index 67a6a9d71a16..147dc0506b76 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_internal.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/client_base.py @@ -2,11 +2,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from collections import namedtuple from typing import TYPE_CHECKING from azure.core import Configuration from azure.core.pipeline import Pipeline -from azure.core.pipeline.policies import BearerTokenCredentialPolicy from azure.core.pipeline.transport import RequestsTransport from ._generated import KeyVaultClient @@ -16,40 +14,13 @@ from azure.core.credentials import TokenCredential from azure.core.pipeline.transport import HttpTransport -try: - import urllib.parse as parse -except ImportError: - import urlparse as parse # pylint: disable=import-error - - -_VaultId = namedtuple("VaultId", ["vault_url", "collection", "name", "version"]) +from .challenge_auth_policy import ChallengeAuthPolicy KEY_VAULT_SCOPE = "https://vault.azure.net/.default" -def _parse_vault_id(url): - try: - parsed_uri = parse.urlparse(url) - except Exception: # pylint: disable=broad-except - raise ValueError("'{}' is not not a valid url".format(url)) - if not (parsed_uri.scheme and parsed_uri.hostname): - raise ValueError("'{}' is not not a valid url".format(url)) - - path = list(filter(None, parsed_uri.path.split("/"))) - - if len(path) < 2 or len(path) > 3: - raise ValueError("'{}' is not not a valid vault url".format(url)) - - return _VaultId( - vault_url="{}://{}".format(parsed_uri.scheme, parsed_uri.hostname), - collection=path[0], - name=path[1], - version=path[2] if len(path) == 3 else None, - ) - - -class _KeyVaultClientBase(object): +class KeyVaultClientBase(object): """ :param credential: A credential or credential provider which can be used to authenticate to the vault, a ValueError will be raised if the entity is not provided @@ -65,7 +36,7 @@ def create_config(credential, api_version=None, **kwargs): if api_version is None: api_version = KeyVaultClient.DEFAULT_API_VERSION config = KeyVaultClient.get_configuration_class(api_version, aio=False)(credential, **kwargs) - config.authentication_policy = BearerTokenCredentialPolicy(credential, KEY_VAULT_SCOPE) + config.authentication_policy = ChallengeAuthPolicy(credential) return config def __init__(self, vault_url, credential, config=None, transport=None, api_version=None, **kwargs): diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py new file mode 100644 index 000000000000..b2e67c71a3ae --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge.py @@ -0,0 +1,113 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +try: + import urllib.parse as parse +except ImportError: + import urlparse as parse # type: ignore + + +class HttpChallenge(object): + def __init__(self, request_uri, challenge, response_headers=None): + """ Parses an HTTP WWW-Authentication Bearer challenge from a server. """ + self.source_authority = self._validate_request_uri(request_uri) + self.source_uri = request_uri + self._parameters = {} + + # get the scheme of the challenge and remove from the challenge string + trimmed_challenge = self._validate_challenge(challenge) + split_challenge = trimmed_challenge.split(" ", 1) + self.scheme = split_challenge[0] + trimmed_challenge = split_challenge[1] + + # split trimmed challenge into comma-separated name=value pairs. Values are expected + # to be surrounded by quotes which are stripped here. + for item in trimmed_challenge.split(","): + # process name=value pairs + comps = item.split("=") + if len(comps) == 2: + key = comps[0].strip(' "') + value = comps[1].strip(' "') + if key: + self._parameters[key] = value + + # minimum set of parameters + if not self._parameters: + raise ValueError("Invalid challenge parameters") + + # must specify authorization or authorization_uri + if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: + raise ValueError("Invalid challenge parameters") + + # if the response headers were supplied + if response_headers: + # get the message signing key and message key encryption key from the headers + self.server_signature_key = response_headers.get("x-ms-message-signing-key", None) + self.server_encryption_key = response_headers.get("x-ms-message-encryption-key", None) + + def is_bearer_challenge(self): + """ Tests whether the HttpChallenge a Bearer challenge. + rtype: bool """ + if not self.scheme: + return False + + return self.scheme.lower() == "bearer" + + def is_pop_challenge(self): + """ Tests whether the HttpChallenge is a proof of possession challenge. + rtype: bool """ + if not self.scheme: + return False + + return self.scheme.lower() == "pop" + + def get_value(self, key): + return self._parameters.get(key) + + def get_authorization_server(self): + """ Returns the URI for the authorization server if present, otherwise empty string. """ + value = "" + for key in ["authorization_uri", "authorization"]: + value = self.get_value(key) or "" + if value: + break + return value + + def get_resource(self): + """ Returns the resource if present, otherwise empty string. """ + return self.get_value("resource") or "" + + def get_scope(self): + """ Returns the scope if present, otherwise empty string. """ + return self.get_value("scope") or "" + + def supports_pop(self): + """ Returns True if challenge supports pop token auth else False """ + return self._parameters.get("supportspop", "").lower() == "true" + + def supports_message_protection(self): + """ Returns True if challenge vault supports message protection """ + return self.supports_pop() and self.server_encryption_key and self.server_signature_key + + def _validate_challenge(self, challenge): + """ Verifies that the challenge is a valid auth challenge and returns the key=value pairs. """ + if not challenge: + raise ValueError("Challenge cannot be empty") + + return challenge.strip() + + # pylint: disable=no-self-use + def _validate_request_uri(self, uri): + """ Extracts the host authority from the given URI. """ + if not uri: + raise ValueError("request_uri cannot be empty") + + uri = parse.urlparse(uri) + if not uri.netloc: + raise ValueError("request_uri must be an absolute URI") + + if uri.scheme.lower() not in ["http", "https"]: + raise ValueError("request_uri must be HTTP or HTTPS") + + return uri.netloc diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge_cache.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge_cache.py new file mode 100644 index 000000000000..07cda1366aa8 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_shared/http_challenge_cache.py @@ -0,0 +1,89 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import threading + +try: + import urllib.parse as parse +except ImportError: + import urlparse as parse # type: ignore + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint: disable=unused-import + from typing import Dict + from .http_challenge import HttpChallenge + + +_cache = {} # type: Dict[str, HttpChallenge] +_lock = threading.Lock() + + +def get_challenge_for_url(url): + """ Gets the challenge for the cached URL. + :param url: the URL the challenge is cached for. + :rtype: HttpBearerChallenge """ + + if not url: + raise ValueError("URL cannot be None") + + key = _get_cache_key(url) + + with _lock: + return _cache.get(key) + + +def _get_cache_key(url): + """Use the URL's netloc as cache key except when the URL specifies the default port for its scheme. In that case + use the netloc without the port. That is to say, https://foo.bar and https://foo.bar:443 are considered equivalent. + + This equivalency prevents an unnecessary challenge when using Key Vault's paging API. The Key Vault client doesn't + specify ports, but Key Vault's next page links do, so a redundant challenge would otherwise be executed when the + client requests the next page.""" + + parsed = parse.urlparse(url) + if parsed.scheme == "https" and parsed.port == 443: + return parsed.netloc[:-4] + return parsed.netloc + + +def remove_challenge_for_url(url): + """ Removes the cached challenge for the specified URL. + :param url: the URL for which to remove the cached challenge """ + if not url: + raise ValueError("URL cannot be empty") + + url = parse.urlparse(url) + + with _lock: + del _cache[url.netloc] + + +def set_challenge_for_url(url, challenge): + """ Caches the challenge for the specified URL. + :param url: the URL for which to cache the challenge + :param challenge: the challenge to cache """ + if not url: + raise ValueError("URL cannot be empty") + + if not challenge: + raise ValueError("Challenge cannot be empty") + + src_url = parse.urlparse(url) + if src_url.netloc != challenge.source_authority: + raise ValueError("Source URL and Challenge URL do not match") + + with _lock: + _cache[src_url.netloc] = challenge + + +def clear(): + """ Clears the cache. """ + + with _lock: + _cache.clear() diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/__init__.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/__init__.py index 718493f543ea..71cad7e66b18 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/__init__.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/__init__.py @@ -3,6 +3,5 @@ # Licensed under the MIT License. # ------------------------------------ from ._client import KeyClient -from .._models import Key, KeyBase, DeletedKey, KeyOperationResult __all__ = ["KeyClient"] diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py index 3e686b185d62..9f81bbdc5a7b 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_client.py @@ -8,10 +8,10 @@ from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError from azure.keyvault.keys._models import Key, DeletedKey, KeyBase, KeyOperationResult -from ._internal import _AsyncKeyVaultClientBase, AsyncPagingAdapter +from azure.keyvault.keys._shared import AsyncKeyVaultClientBase, AsyncPagingAdapter -class KeyClient(_AsyncKeyVaultClientBase): +class KeyClient(AsyncKeyVaultClientBase): """The KeyClient class defines a high level interface for managing keys in the specified vault. Example: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/helpers.py b/sdk/keyvault/azure-keyvault-keys/tests/helpers.py new file mode 100644 index 000000000000..8acb3d5f646b --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/helpers.py @@ -0,0 +1,72 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import json + +try: + from unittest import mock +except ImportError: # python < 3.3 + import mock # type: ignore + + +class Request: + def __init__( + self, url=None, url_substring=None, method=None, required_headers={}, required_data={}, required_params={} + ): + self.method = method + self.url = url + self.url_substring = url_substring + self.required_headers = required_headers + self.required_data = required_data + self.required_params = required_params + + def assert_matches(self, request): + if self.url: + assert request.url.split("?")[0] == self.url + if self.url_substring: + assert self.url_substring in request.url + if self.method: + assert request.method == self.method + for param, expected_value in self.required_params.items(): + assert request.query.get(param) == expected_value + for header, expected_value in self.required_headers.items(): + assert request.headers.get(header) == expected_value + for field, expected_value in self.required_data.items(): + assert request.body.get(field) == expected_value + + +def mock_response(status_code=200, headers={}, json_payload=None): + response = mock.Mock(status_code=status_code, headers=headers) + if json_payload is not None: + response.text = lambda: json.dumps(json_payload) + response.headers["content-type"] = "application/json" + response.content_type = ["application/json"] + return response + + +def validating_transport(requests, responses): + if len(requests) != len(responses): + raise ValueError("each request must have one response") + + sessions = zip(requests, responses) + sessions = (s for s in sessions) # 2.7's zip returns a list, and nesting a generator doesn't break it for 3.x + + def validate_request(request, **kwargs): + expected_request, response = next(sessions) + expected_request.assert_matches(request) + return response + + return mock.Mock(send=validate_request) + + +try: + import asyncio + + def async_validating_transport(requests, responses): + sync_transport = validating_transport(requests, responses) + return mock.Mock(send=asyncio.coroutine(sync_transport.send)) + + +except ImportError: + pass diff --git a/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client.py b/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client.py index cec73c1d9d8f..8cce3b685889 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client.py @@ -7,7 +7,7 @@ except ImportError: TYPE_CHECKING = False -from azure.keyvault.keys._internal import _KeyVaultClientBase +from azure.keyvault.keys._shared import KeyVaultClientBase from azure.keyvault.keys import KeyClient if TYPE_CHECKING: @@ -18,7 +18,7 @@ from typing import Any, Optional -class VaultClient(_KeyVaultClientBase): +class VaultClient(KeyVaultClientBase): def __init__(self, vault_url, credential, config=None, transport=None, api_version=None, **kwargs): # type: (str, TokenCredential, Configuration, Optional[HttpTransport], Optional[str], **Any) -> None super(VaultClient, self).__init__( diff --git a/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client_async.py b/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client_async.py index 4d4614d46fe1..f99e9609e3a3 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/keys_vault_client_async.py @@ -10,9 +10,8 @@ from azure.core.pipeline.transport import AsyncioRequestsTransport, HttpTransport from msrest.serialization import Model -from azure.keyvault.keys._generated import KeyVaultClient from azure.keyvault.keys.aio import KeyClient -from azure.keyvault.keys.aio._internal import _AsyncKeyVaultClientBase +from azure.keyvault.keys._shared import AsyncKeyVaultClientBase if TYPE_CHECKING: try: @@ -24,7 +23,7 @@ KEY_VAULT_SCOPE = "https://vault.azure.net/.default" -class VaultClient(_AsyncKeyVaultClientBase): +class VaultClient(AsyncKeyVaultClientBase): def __init__( self, vault_url: str, diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_backup_restore.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_backup_restore.yaml index 650ad9e94ca5..788cededc314 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_backup_restore.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_backup_restore.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:21:49 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: !!python/unicode '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/ada51ab3a12044228eb5220085ed7fd1","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rG7TKs70mQqtxO-Xbt3k3pXWYYotG3GRgJlc108EDEuZWjY-Itzbda_DXmOFbXOTdBl52m9DCHQOZx8RK8p98muUKF_Dpmh0S1QsmfV65_5ZtY-lOOoyCbQOhjh_AZxcJ0BQ8oMBUyBFOt1ljP33ABGZOtVYLZ29ghk1FNtTN5UK20nnCtNFbmDlmtcxjm9d44pwgtQlv8Qdwqv2yS-24qnU-VXpkW_1Uj2zIXBbgPXMoCIZxBDq5NPKiAczHKEBqu-As7EqTeuSkWtB52zzDaqCWD6yICbxiRDiPGf0g2hCaftFU0AtOOprQsnFTwqw9BYwitlehgY6ap3yoCkwpw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/e37af5aca9214f0da06fdcdf9b3673f0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3whdGIk7Kq6uXQsBCR_9LkLgcdBzTr_YUP7862Gqs9vc2d_dJcYw7yG48R37hir2x76m1FOETbmgJw-tKcxzNvGIDxOvM4xCPAvJhx3rLN96-sdUStIcVmbiQ3rLsiaRdjvwHfS4ZGamgHGFDBuupS6Qc-hFvERUOVNUDZMEOZ7HEON7HCD2_o7QUFef7JLIz9JYBBu1b2JU0hgfINkID7NGOhzIBvBRy0phEycM45Fjy8m3w2vn5bKKWEfsS1QmW3QUjX6pCY8UE0PgB0QJ5hJfYiZUPi7vot6I4tJlNmx29ngzJ7qMKbxxnTVxHjQjB1GLQtVBCp5pAaAM_GGv5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -63,12 +116,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/backup?api-version=7.0 response: body: - string: !!python/unicode '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmZiT0tzUV9qSzZBbFlsbXhPWURScEpFUDFpaVRzMExtZlg0aHMwS3dyeGpVWEY1ZzRxcEFLbTNkWk1nLVB5WnR4NFRUcnhqWi1KOFJIRjdwLXNSbFlHbWhFclcxWGZsb0VPajhWQi1kcGo1eEQ3dzdrR21jVkhPeFAweG5UdEh2NGlKNUh6WG5WTkxQbE12MkZRLUJhdzlVVHZRSFBWcDJuZVN2OVZGTEJOUmpWNFo0Mk9lSkxSYy1rcHdDTzNhV1V0Yi14amdwVU4yU09DYkFVN2RmS3ZGYkRSbWkxbW5xbVIyelYxSVp0cm5jN1E3ZzFraS0zNkNYLUNCWC1yb21pOXN2WFJwN3phbFc0LTdXVEt3WjAzdkhPaWtrZVVJeEh4Z2pZellac2MtWkdwQWI3dUl6T2c2SlViT2xiZmFCSlN2THVNam85dFR3MXlQbWhWQUMzUS4tT0tQWm11ZXhkQndIS2VKY2xjVUpBLlZIRG9SZnBvMjZFMmNlY3dnaTA2RjZEc21tQXgtV2pwZTRCT1FMc1BteWd1Vkxucm5DT1RIay1EMTdNbjVzUnJqTHdIcUQ3X0lnMC1UaUZrRDJOUXRaZFptRWVILW9mWFdNNUVmM3FQSm1PcDVQZ2QyMVkzWno1SjNDWmpITE9Dc1ZISWotM2F4Z1dQQWdTVkpHNnNSWG1uZ1BPMndEd0I0MVdyTVRwV0ZuZVVPTlNJbWl0RktLWlVobHRTRDZEYzZVeUhqMmtnLXA1QXhEeC1MLUl6Z2dRbUdMSXlGWVhEZVRvenF3VWRRT1d2WUpUU1VMNEI5NnJHLW5SellHdlNwSFRiM0lReGJOWC04Nk53alNlbU4zcFFrTFRwSERYYl9ObjNrTVNQS0c2a1hHc2U5bXVXWlFmUWJoaFZBeVYyOTMyazJfX21ENzlQSXpiaFJQcXh0ejZmeU8ybkdwR2xmM3VPR1U4dkNrN3BTMGVndmI5dEM5N0Z5WThDTzBEMVNkQjBjaFdJNkw4X0htbnBFNl9LS0c4VGhqOTFwdVhuN291WWctQlI4ZnZOYVFENC1SU2VjNkJXZ0hRNEh4ZW9reXFnX1hndmhQMzVrcDE0RVJJTGNzMXRjdTdHTGUxOVhGUW5pR1poaEV2TzYxaTMyVmdTVXZYc09XN0owTVRUcE5HR3ItNlZEZl9ZaE00XzNXMmptSXlUXzAzSXhENWpUNXU1UHVsalJqYkdkRUVzaVNsRVZVR3RuNF9hLUxtdmtzRFBjM0xqZGk3T05XblFHRGdVVUpHdlIwVXpELWQ5MmVHZEEwUzlESUFaY1JYM0NZSVJtbDZSWmtwdG1LSHVfVnNvNzYtczU0YWpkMVBBdzdISVg3dTJUU0Q4S3BuM0dmMGZmRnkxemY3OTdjUTl1bVY2SVVFYnJLRGpvRjNSRG5CZldKTFRRSWowUV9SZzlPeTlkOGJIVXFROTBOSFpUa3M3LThFNDhuV1Uwa2p3Y09kV3dFRnAwVTF3RVhkWUV6QVJSSkZ1aGYyS3ZGRDZsTEpWRVYwUVZXQ0RXbzk0anV3bVN2dFRseXEyUDVZc0t6QU03ZEVfM3AyOXJJcDFNM2pNOHcwSUlpMzJTZHRMSXozYkJMdWtwcDVFNmhGZERKc2d5VVJldTVRQTh3d04xbmVWdHlnQmRmamI1Zk9ueFVMRDZ5ajg1cUxwWmwtNUJZTmwxQjFyOWhpamF6cTJlV19BSVY1NjU2RjkwV1ZaUGg2ZGlJeTRMaTVxaEdWUEphWFZYRFF1RkJmZEktVUpkQTNmZVZZSExqbmE1VjdUaXFLY3ZTSkpseG92WU5UYW91dkFLYnBSanJlRmM2aHlrWnJwN0tyMGQyYk9aaTB4bURPUVF3aW4wZi1iWGNVcXdaVkhvYUIyblJFQXhFU000cXRhenBreWdHemVQay1IeEpRRE5DSXV2MlV4RGN2UXpMQ21IQjkxYW5YU2dVY2NuZGsxNG1VRTBXYUJiS2owbHdjOUQ0UzhTZ0VDcFZ5a2l1c01meko1Mkp4XzBsVHZZMkFkUjUxbVlhS0RCWVZiazA3cUFVX191NVpNMWw5SkVuRWhaemZsWjJWNXBSQnJsR3A2MVc1R093ckZ1OGZRcjVHblc1TmV0WjFOa2g1N3liMVQ1MUZjcktTUTY2U1hXcTRxSGcwcTRDMkJydE42U0IxNHZsTk42LUY0dFUzSG5OTEFfWHJFTmNzbnU3MkVnb0NnNEwybXhNNklBTWQ0ZXhnUDBhRUlyQWEwdEJMUnVKVkVyajlSaWdwWEVfdHk3c0JPSEpVa2FTSUVGTDNJMGJ1eWVicGpoZk1hamtXTG82R1VmTUxlbEwyVUxMSDZtWlVBSW5weURBYWZ2VE1tZHItOG9Rb00wSGl4elJRSTF6MExmZXRwZFBpWmJVbm5OVUhJanRENEVESnExa01qc2JvU0lfbi04c3FjenZTUmo4aTlRalNRalpDS1pjOGY3TnRWRGFuYi10QzhiazVWZmRJRlBXRE53emVLWFRkeExRZmF6N0Jxd081MXljYmdpZHR4LW9hcFZjUmd0dmN2THIySWotcXFWeW1OazNxbVNzb1h4SVFyY3NaZU93aGpxZ1dtUVRrNmlXZnUzMkVMRXAwME9WQUJUWVVQd21qU2RUWVJuWEhsWlZsZDZpWXFiQUUxNm1IWFI1eUt1dG5RSmk4c3RUWVZQSExjY3IwWTB1dWdBYTlfS1BVNVVPVnVfM2VOSThtWVptQ2VrVENnOWJ4Wlp0MjJERTRPb1Y5X0FzZjFDLTlENll1NmxEYVVXVTgtckhDR2dzeFgtYzhndGg5ZlhmS2g3Y21ReVM1QlhxSnNLdzhaeDI0RmU4bWJ2UC15SlM4MVBGRnRYaWtHMGlWcVotc0ZHZTVBZ0M0TlpsWmNQc3Jnb2RIQjhNSC01aGFKNm9Rd2xKTFB2Q3Q5b3NUYjBhWVZjLXRtQWxTSS0yeFNLWjFQYW83Rm1UOF9sZGE5M2VNS1pPOUFfcDhDS0F0d09vN3JzTGZRSW9jZ2UtZGM2RS1jVm1xZnEwZzNwb3VpMnljSGNOdnhYR1FSMWRpVTEtNXZ0RnowVXE2aVBBeW1HY0Rya1I4VDBCY01fLUZMU09mMUZocEZ3U2lzUzg3OWctbG9wV3Z3bW02U0tuSUVsVU9NTWp3TE94NVhLS3RyVE04N2FJRWZDTDdKWTRyTnA3QW1CNXRTanotdUtJRlVwUHpfLVVCaUNWd1YtMTRvTk5KbjRmWU16VXpsZ0d2MzNPNlU4aDhqNWV3T3JHZ2VDMS1DT2xTclprdkIyeVd2Vmx2Z3Rhb0ZuSEsyU21rNE5Td0hIZjVaRHpHLWhrc0FOSGJSZnpTOERPY1hPLWkzWDVSVF9WX2NNNXhOMFlGRWZnSHFobDhPUlBjUzNHMURtTTU5dlktcURxTW84UGt0N0JEZHNhS2ZmeFpOSTNJbHp5S1l1cUF0UXduSXJoaUFNTXE0WEJzTWVfcEtCd05fRlgtcXozYk1JUWNtU1pUYWhHQzhHOXFJU3AtbW1WOWQtTFBpOC1FVkdHSWY3czQxY09qQ25VSzFjLXdoanJxUDRQUF9MZ1Q2RUJiSUwwcTQ1Z3lJMkU0OWVBNWJIakJqa0xHcDRfcExDbDNheE82QmRFNjNPMzVoMld0UEdyR1pzU0VobVJWSkdrV3hxTFFHVXloSUJZSGZVd1N6c3h0TkJPTGZrRDJ4UHVKak9zVFh1TDF3b2s5ZElfV1NKZzFwZlFuYy1HZEFJbUtFS05lWWVjWDVnUjVFODh2TDdnejZWRWRGZTYzRjItdVR1SWZRSkpCSXVnLU5yb21aZFdqWkxUMVI5bkhCbGxKSU8zZTQ2eEpjN0dzMVpKLU42aC1JYmtobVQwRThYeDd3a1A1SlRwYjktdmZITDlab00wc1k1N3Fjem44WVFzSWNfY2NfQklJU1RFRmZTNVpQWWU0N2s5b2FMaWZfVTdGODJYd2lwaHlVME90bURvRE9jV0pCRGZBcUMtQkxIVnJjWFhHa2dscmlCMDlJWXhnN01zVTJGZTNXSFI3VWlqUV83WGoxeTZwUUNONmVNVnB1TEQ2aGVMRVBLUUp5ZHJkcktDSjVUa3YzeFBqOE5sVGNGWVpvQmMxX2dsbFA1YmM0ZllUMTkycVNmOHhobTlXTElWS2F5SVNzQUdZNjBkYzk4bHdycnoyNi1yd2tqY0Vadzl0OHNPYzRRVjd2WnV4cklOS1BsbGU1bDF2ZHNaMTZ2QzNmNWZfeVZnbE9PT0lEakRfRTVYRG9iQ1oxd2gzRFQ1ejNaYXdpejhXUlh6cGNHZDZRX0dtWkdPYS1TejMzd1VSS0dHbC00VFNaamtsMnJVRExYbTRlNmFENXpnb1ozY0laVzVRSGdYWmVveExRZC1YZDBOcWMtZVFnV3hTRDJwcDI5VDNyTGZjMC0ybWhYQTZhRFRjRFhVcThDdEZyM2N0MGJVRHluN0FhNTA2YWhuTVVzU3h3WG9WMkZYNjJ3bFdaQ2VwUEloSTRRRDh2MHZUeUgwNFI1alhTcEtwdUJmWkZsRi1xNndHSXREOUMtdDJkMnJRbVZVY0NnclF0dXBkRldQclh2b0ZXc0gydUlPMzVOZmZ5aTU2d2ZtVWIwUW5ib3NRODNwSTdRZVptVVNDbVNnc1RCUWJWak1tYVRvNVA0SDY5R0l0VHpXa1k4Y25EbGJ3Yy03QmRGcnUzM1BDck9ya1RFa08xa1JtaVVOLW5WdnRmZEpiYnJsR01EdVRmWGpFVzZPVnFJYzg1cWdpZVFvY0tCajdIekl0TjNfTlV2OTF6dkMteU9HRUswbHUwMzdKU0d3TnhNRnI4QkpGM3YyYVBxMWxabVd2ejNCbXVKaEV3UUxadUNkZ3UtN2hWMGNsS3J0V19sOGpIVU8ySkVwV2RvUjY5ZkNEVjYwMU1XaHdkZDhhbVppZTZxdkU0Q1NwTXhSTHRUTnNhRE9fNXlJeVdseThYeXZmXzJ1T2Q5TmJuTHc4VGNoZTAzajlZUHJqa1pWaThJUThabFFWQkkxTl83T3NfcE9PTkdxVDRUSFNJcXF4NkpNeldXbG44REtJMGxMQkVWTmlsTXBPa051b253bExMNEpjd1VQczN5dG5EMFJleGxaUy1rVzM0RWxPWTNpMnR0Q1htT0lqQlV3MGRya1dtU21KcU1TRWsybUNKRXdHYVJ5ZlNHNkJPQlppU0dqRER5UkVlYmpwanJrSTFQTUczenZFaU45TzlRUjh5UGtMcUNwSnQxbHJQYl9LVDBqVnRXVWtSbkhqQ2xyelZoZE8xalIzSmxUcGIyTGs2S1JzajJUVktQOVoxOXpNdUFIZFZfWTdLYlNmLWNBSU5ITmZSNm9QSEY2VjVmaHNBOW8wT3RZcEJSRkd6RDk0d0h0Znl1UU9CckdibVBDUTNXakdzX1I1MVpJT01aWlJ2N3hXVHdzTUZNVTRselRDX2xmRnRkMDRKU0I5bGlFUEhwZ3lkdU9yS1JSclU5TEdoNmV3Q3NkZjdHaUN5aU9Kd1MyczdhenRDSzJUR0NDa29RYlpPYkpNOHhtOXR4eG8ySHdHeWY1S2ZReGFqWVF6WDJTRGVraUZnVUdhMzUxd1FJMC0zU2FkUlRFbFJfLVMzdEVON3RvZm9uVm44bER2dFNIellBUmxiWGpSRHR0M2ZCTERReW1VQ01vY0lpaHl6d1hJTXNjQ3hudGNyd19zeDZQcHhYS2pTZHdwTmUwMGFCaGhteFhNRi1LYjlKdWtGM09tV3FjTm53VlZKNTZmVTRwR0NPLXU2eEljVUtJa2ViSTY2SXZWeDhXQ01LeTRTOUtBdEhKeXc2dXh1WExFdTZOSXowc3l3MVFNeERPRVhraUh4SFhWc2RwN0loN2ZOcmh6bkpldHVmenNwLUd6eGJfWUVBTS03M3l1MTFoU05CbXNMek5FVnFaZXlLeUdxbnp0VUIyRlNxQzJZbm9sTWVURUdDRV81a3lfenhocXNmUFltck56RE9LOWpuOWQzRjh1dGxJM1FRbk9EcEdpV1h6MFlXdm4zdGZKelU0VlkzVEJHbEw0YnNzaDhiQjZ3T21zU0wzUGFCeVhWcWFpNXN0WWVtNlI0Um5QXzZLRWFwRkhWdThkOXFrMVZvYS1VRVZXWklBWWZEYVl4TmlMU3JLYmtTUFVJNFBPQUE1RnczM2owUE5fRFdlUmdQalFVVWJENXU3NlotVE1XaGlpenJqSU5wcTRuSVBMUzhhN3gwdGJ6LTRrYzluNTFjaE5iQ0kybUoyV09EVUk5dTBUREZxSDk4cE9xUW4zdkpINjZiTnJLS1BYcmpwbGZscnJaaFlhZlhqZmVpenBvb1JHSnNFTU4wWnFwck9xc3cxanpKak1qeE1aQjNCLWl6d2VYeW9tVVJ0VDNFOExoYTZFTllmUGEwWGoxb0ZuTENyeTJvdlE4amFkbkRyMnVIdUxEWnlyQUdVbi1kb0lmQzVRN3VQWGY2alNESzU2TE83b2JjQ0JzeW9WUWN2UlA4T2R1R2hQbF96d1Z2ekpIb1N0RTNDRV83b0ViRUlvMGtMaWY3ZEduVWlRQjczd0JhMGtBaVZLc2FtcElJRFhuVDQwX3hhVTgtQ292aFQ4ZHNxeW1iWkxVdkRXbWI0VXUzYS1fWkI0U1V5WVFlZnpZOGxPdE9zOVltdGwwU29BZzZLOTVyaTBncVhGeXlXY2NiTmF2U1hqX1NxdU43RzVyM0xodXFrOVJGYlJxOUtBX1BaZEU3VEVRYmt1dUFQcWpEenJLNjloQzR2NGFzMzEyRDZ2bmFlemNJVXJwT29za2gtVkRERkVsZEdidkhjTWF4X1dyejZkaXZiMWxCTURzdGl3NnV5cVpsOVo3YmZlUnBsRHJjZTNqdEtkN3dicHF6and5by1VMHdnOXJCc3EycjZka2M1ZXh3NGF2RDUwZ0xuV3lNdHdOLUs4LVB4aEt6SmpabWVjNGxjd3FPZktKOXduemV4dnRpTllDZWxCOVZLNjQ0OVZld0xFNHRtMUpUMktrQnlQdlJQeXNsVnlFVlhFV0hEYkJuX1ZTMmZ2enR2dEl2cTBDMHZYVVF5aXZ2TXhsbUNhWGZtOXRJbWNkeDVoSlIydzBDeUwyVHdXRmF0Y1paMWtndWFseTlfd3F3VU9uN3Zqa0RDZDVyLVhlNU9Ienc1alFfXzBYRWZGT0JOekJ0bkR0RHFvZG9YNURvb1RsM2JqVUlOTTZWSXNtUzZGaHJHZ05mRTVTR1V6Z1JPU1hjaFlzOHlLb0xVZm9NY1U1Qy1OVFZrdnE4a0lvNW1FSWlGZkpUN0JWLVlxTm1aMFRyajlMSDdUZjRVLV9aSW5EazRSTGwxMnY0anAwOFlCa19hSy1HRDNKRFFmTjVjaEdVUFAtVF9iODJaM0xaZENQSGp5UTF5ZXNJbm1qZzRia2NJSjR6THlqQ2RBREJ1Tl9sLUJqQ3M5bzAyblYxOU9KWWRUNlhFTTc3cGFOZ0pST090Y1pWeHNHcVlTb2liQV9kc3FycnhqVFVqLU9FZjVQUEY3ekVnMU5lQV9qems5cDV6azFSLV85NkNRLWtSRmZGcEJ3enJ0SDBFTGtvS0h0aXliR1ZfYTVYaFRfZmV3SC1xYWlsTHRlUW9MVDhSeDlJQnR6anZiVElIM211QlZlNHhHMHFMTHZTYlVkLXlIZVYtVll1ZUZTajFWa2Z3S1RBYWtSbXZhcDhzX01Zd2hiZ0VWalRiX3pJZnJwcXJxUjBBX29BcEE5Sk5YbzlrVk5PQlFRbmhRb2tnNTkxS0hFcEx3bUQtM3E3UXVuWVZDM2ZGM2NYcDhRaFI3RVdmU2xDX01ISGtwYXN3ZzhEdFgxQmxzLXRWZWNiTU9ET0xleE9TaWlLbmdnQTZnX0tXS2hCRXVvNWhLZ3A1N1lobTVvamlkU0xzWnh5X19zU0I5cm9nV3N1S3h4dU1hRjNyd3RlUFU3QVQzUlVoSXZtMDRMYmQ2OGhwTHhfYkM4MldJSFYyc3ZCSkh1b0pPYUJTOXpvQ3h4YTYtZUlla2RQNEtmeEIySnhQcHZ0QVdZOWRybC1XQW5DeldKUEMxS3NMaXppcEFPSGllbVNfRWxKS3dQaUViUDhocXpYTDNsQlp2cWhtTHhxU2NwMUZ1R0QyWWlYWTE4RmN0VTNPdFhicmp3WjNaZG9aUjBaRnN0STcxZmluYmMxemRGam5Ecng4STRDcms5M0YzdkI3VmFKNWRDeEtCM1Rnb3V3SVVOd0ZsMEJqc3RuSVRpeTZnYWVZVC1jMzcySmlpNzJQbFFqU2R4UV9rUjRHb2VFR3pYdFpTSlRoNEVRS1BvOVhmbVR2ZEltTHp6UkRtSW02cWFZTDU3UkNWd0JXaTZlY1pzay1BMmRsaFdaSWVuczhIUkkxWkhQSEk0WW1vXzJia1hmbUNrWE9uWGNUaDFROHBPUUtUTUZhcFlCc0E2SEs5bmdWUVV5M3RiaEdTVVNvaWxNemFGTzVEZ1JMZmZxNGg1aWtTREV5VkFGOTVfZ2M4NHlUQXdtclktZlBkMmxtTC1IRFJLNDl1UThTZXhlSnFJZE9qTVBNYmc1cjc3dXVqMWpBQzVnOGx1TVZfZWlpQ1E1b0twYlhqanBBdHhFZTFJSjN6QVFYQXhpaUFPMHF6ZU5wRWFLV1F2bGpUSXRQcDNlWENabG9ZdGVicEJVM0VzMmtmYlFKaHVGMGtSaWxKOGlkS2JhSHZvSmxKQkJnaFZpRmFsQnM1TXVBUGxwWlVRbUZYMU92b1UtdUdPNTdURnI4TVA2cFZXR3pNV0ljLTJnd3RuSW94UEZsS2lZaDdwcVJPbG1meEpyMzh3LUxHT3F5YnRLRk9WMy1ra0pOejVTZVNrX0xubXNxUzdHOGZFUnVKTjZ0N2p0WjBMLTJWVVFWekE3c2NpamR3OGJST19CLW85MTMtQW1DMmtXb1NKY2Y1VWVlRjh0VU9FenMzMDFINlUzbG1sWUVtcmdZamJKNFJCQXVDcjY0a2N1YnhyT0hRR3dzblIyWUo3TVZQaTQ5RUFNYVVON0JkNk1INFkzTUR5VTc3aWJKcG9adFY0REVmcEEwSkZ5b1Q5QjhpR05tMTFkRXhuZTFhQy1vRXNoeGRoMDJmRXVJWmtNWVRkYnRTeTdBMThOdDVHRllFSE9PY0dnZHh6SDduSURGM29DeWJnVWk5NmxxNWtCSkJ3Y3Z3ZHRjRTM2dWtnOGdfeDAtTGdIc0o1Y3BuM3hjX0s2eWNodzFQajBIWl9VRnh2SW1DaF9mTGVCalJBdHRnLVQ5LTBSc1F2UEp1Nm5fVGEtMG14bzhDUWdEanNBV2xhaVlNTjhxTWtrWnVNZEhxTUlITW5sZndyWE1kSmJDWkNZR1U4dGhMUjkwYWtpS1VzVEJtcGZtemdTQUdVcXpOZjIycUxaaGdmdXBkMWFqWjBGU19vSWtoQ2x0WC1SbVNmWnc3RVV6Y21Kb1l4bmI2Q2dqR09mN1pfbENfMlY3NnNkQ0ZUZU50cE1zVmZQUkFnaDZRd2VKY1cwVURlbGpxc0RXM2FrWGpDZzdwSlFmUnJLOW0wbzRnMmFXNy1faU5yb1hVSTZmYVNqTFpnb0FNWm1GTUxsZjh0QS1OblRmYXcwTUM1NUUxTGRxeFVESDN4MUNFcmVaejN3NEl4cVVSbmlPRl9BczdrUlpSa3h4a3FfR3NmT3BKQzNwaE5FNUxWeDNEMHdwaTBBUXVmNzk2LVljSXdPemd6SmNoSW52QmxfdVJUejhQNHUyWUhjRDhJVEl0ejdWaEFQbDRWMTZjSERFXzA4TjZicnRoQ2ZWXzh5N2RudVJTLUJfZVV2ZXIwRl9fc19pVVhRWGRHOTh6YV96TllZTjRPZ0R5NjFiZS1GNi1qTFQ3VVFsQVBoQWNNZUw1clZnS1E2cU9QTEVVa25fb21VNUNBUXNxTk15VmpCcDYzUTVYclhyUW1neFlGdUpwOWkzR1NQeHY2Q21JVTMzSVRVWWJFMEI2UTBSbG9DSG4yaUlxcmd0MGppUEhtTk1DSS1uZHBlTUQ3dVJpVDczOVFweDlub2Z3UDdsalByazRIRFZFVTV2blBhVXBvd3hOeW83ckFPV216dGJoc2ZTaXVRSDBDdzltRVYtcW85aWlqaDg5LWdaZXVIaXdLblNGNjRZT0ZxSDgxVXRPOUFScllUWmhGNG1tRmVadkxVb3RwWlI5c25TRWw5WXhhRFJBeEFhbTdLWGFJbmNlUkNBYUtDUGt3UzFRajhsMTk5TmswNmtRLklqZWR3RHYyTEFXenVBeUhmUlZkblE"}' + string: !!python/unicode '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLml4RXd6WGRoSEtUT3BaMVY1Qy10aEtCU1NTVUdEQlBEcnQwRWVmUjhyNkVPdmJyM3lFYjdyUEYxUjRWa1B2QWNQUUFob0hWMkhydDZXY2Q4WDhxd2NzUW0wektnR3BKbTFkRmFGV0hvLUlDSC1SSlZiR0RzYzZTdGlwaDAwdUtqUkZTdFpqOGo1d3ppR0FrVkRQSWFKTWRsajR1VTZBdnFNZ1dhQmtGcUhjdXF3bE5VTE1mQWFBaVU4M0xGMm81RjV1MXVzS210ajFfUUhJNHhrT0VLYmpfc2dvZnZpVXFIVkNNOGdOR0hYQzhjYU5zMnlTRUplRnBOMHk3Sjl0dV9CVFZfRDlKdV95djJKb0VyWW5NUG1ULU4xTWpjQ1RMTm44Y051UzNqNGJmbVgzR2I0VDAtZUJTWVR0dGxLanE3UE84QTRpekx4S1ZJRVBJSUVnd05jZy5zcEpPak5XYXQ5RXNxejZKcVJkeWp3Lnk1OGt2THRoRnZSVnFma0J3NXEwZVFjSGp0SHJza21mclNZQXJ6MnpXR3dJajN3eGhoVGM0UG5SanVhV1plZFlOcGYyYkZNVk1ZNGgteHA5LWhKVVVReldRY0NkanV1TWUxMjNRRHFPUGJ2dGFBeHI2SG9LY2lKSk93X1BHTWpBRjZiOUFYUU1FaHpBLXBMR1VudkhqTTN6VDVYTG9zY01mZHRwOTNmTEIyWVJaN3J5TUFKVkRuOTVJNzJ0Rm0tLXN3LWtqQmM1YXhDMk5YQktoalZrTDB5Z3Bxa3dGX0Q2LXEtYWw5eFE2WHFyNHlmS1EzLTBTUUhOa3RSc1Itc2VDU2x4d2s2a2tnelBobUdaeF9tVktrRmw0Ykx2NmtwVml3YTZHdlRlQzdvTUFPYjBkMGdvQ1gyTkxvakN4dEsxdDRxallBQkt2NEpxX2VYLVlaU3FGUnpFVmF4NEhWWjlRMlRicEMyZ21Gbkpza21GdDVrSHpIRk5LVF90eWF1b1loNlRvVlJKbEYzNzItX2pUU3pfX3kwejB3YzZxQmhnLXowZzlQQ3NmTy1ybFdkVkJpUENabmZxdDhOZjA2RXFQSnRucnlWZHlabmxpdkJwV3FfRU8zbjBlVktwTU1MNWI5WlZLZ2thcnctUUxiNFhGWjhjdGtjY2xLOFh6OUVEZzZvV1JNdm9UYTlINXFkRy0ya29Rc0ptSGFCZ0NzMFlXbVAxR3hZMFhzT3lFNzlvaUYtN3JZSUx5WG02ZFBSU3lsdmRnd2VGY05uTUtYX2M0N1otMGVvRVdmeHp1RlpMUDh4RDdsVHcxb1VrUlh0LTVJUld3bkR5Q215UzdzT0c3WWU2eDA1b19uTDhZZVNBNUhJQ0dTdS0tTWRBTEJrWXVHRHFQNW1JQUNuU2ljMnNjal9zdklVSGZIVzBla2ozeEdUMGNBVUxBTlk5cThFSTZyY3Q1NVozcll4RE4tUjRYTTdJalVCdl8xdlkxQVlYVnVSVUJya3UwTlpnbHQ5SEdSclZCNkh6TmRJbm9VZWVCNnlMRmFFOGVjTFFXdmYwbXh5RGFibEI5ZnM2R21LZUFxTlhyTDdqZUJKWDFpVEZyMGdDc202dV9VZGpyWE56ZjNLQWhzQks5OTFOSXdOTkhKQml4b1FOVmVxdm14ZjdqQjcxbVdtWElBSkVzZzFfSFd1R0QwM3lGTlYxZjFtMnlmZlZFYUVLUlByandhbWw4eW5kSk1NU2UyUV8yNmlXU2NaVzRsQnVGaGQtRVJDUjIxdnhvUUhwb0kxRTg1bDJBck1fUUZZaktHYjI4c1NOeW1hUy10MmVidXIzcHBScXptVm9Sb2gtSEZYR3N2aHAyODdXNFE1TnhpQS11bXp3TTNqRTV1cnpJZWlBd0NkdUM0MjBTRkx2dFhtLUdHdkd0V2lMRmRQTWNFMVNyeUdQS3B5RENFSlVXU05XTE16Rl9SeGppTmdSSzk3SlBfUUlac21BZFZtWi1zUXJXbE9XV19NTTRTZERKaW12TlZzc3RaN0dRN29tdUlTOUk5Z2NyZHNrQkNPMl9WMnJBOFZ4aXprQnQ0OE5Pakl5NEd6aWNSV3RIdkhLdTRoNVpTMGNMM2ZzRXF4c19lVkhWMEdSWF9ob1BQRmlpVlVaTFZqb295dkRwZ19IWXN2Q0hzQi1MZkVKLWlDall3OXVEcko2aWJ4a19wN0V5UmUyUWFMdWRWLXdJYS1UMU5wTEdHSUFqNjJydHNrUmItSTVxTzZzSFEzT2VZQ2VEUm9JYzdBVUttMzdTZ0l6ODhXOE5HUFBZSDBGNXJ5N2xQNUZZMFNSdFIxZ3k5cFRPbjZSR2J6M1JzWWR1QWZ2Q2V5YTBRX3N6WEwwM1g1SDFabE5xeGtYYUJUelgxR3pqWjdJZkFwaGJ5R0VmXy0xcXY5TFlqb2NXVnhodHJwRHUweTJBYnpEN21lcHZNNkoxYnFVbGp2UUpfQk1ueWtEVXBnZWZ2NjY0RmhhaWpYNVJkM3JKMHh5azVUaVltRzd1cFh1aE1lbC10RG0yOW8xeDI1RGI2SVhwZDNRNi1uNXpzektSQXZJMy15aERCQVRQcWpWNFk3V0I4LU91OVgzLWJTc282OUFETUxpLTVyRWZvWWhjM2g3UlJpcVBmNi1kR0hBSjdRaG9qVGROWklzX3NKN2FQT0s4cGtNcVg5SE9qb2tiNzZsQTJHZldCMDh6a3p3a21SSHJSOFp1TlBJR2FiSmNNYWloaHhUVlV3a2JRd0VpTVFrREZKV1VaQzZ2c19wSjVzV2ZmR0ltRmtGR3cxSXBnVXFMWWtpdVI0a0RwX0F0Rkt2Y2c4THBNbnQ0UEJlcnd6WC11QUpGelVNbGR4RFdvLXpDRUhpaGRyOG1mbW9yWHJMOXVPSmFWcDlvUWRXQVVJdXU5WVZwbGF4R1g2eFFqMXF5NFRFTzdwQ3dSdWFsMFZwdFpqMmN1MmRiX0sycWNnV0o2Z2xnQndhajRPT0dfeGtEUlNyRW1yeHpfNVdoM1YxODFISmVlNE03dkdoS01saDFWaUpIZVV3a3VCRTFWcDdLWUdPMUJSSnZpYjV1NjFIMlo1SHJwUGhBMTJKTXpwT3lrR0l4MUpGMHo3aXFpTVQ0cnJ6SDR5eXI1bThSZzIyaUtEaGtQajZObS1BVmw2VnlRRjg4MTVObGRUSTZpSmNoTGd6V1lRQzVrbXhpcUwxWWVQdjh0b3lfWG91ZjNRbUxSWERMM2FPZ1JQcVpDMGR2NGUtMEt6bnIwZUNSWm13Ql95X1ptMVQ4andJUWhDVHBRQkRRQ2pzcWN2NWk0R2ZLVmtVb244SnB3UVBEaUhaRVRBSVlrNGJMMGthaDM5NjlXcFJNUGJWZ1ZmTDUwc2RKNWdpNG5uMkZCaFlpQmJmYnlzS01tdU9aR3UxUTVuR2s5NkxUYU1Ob1ZXczNiVGZVcmZGcGJNVHFNb3RzMFlNVzdaR1g2QkZVQVJRZ0lRUllVVXRBeE9ncmtIOG9BVXFVRDQwYXZPX25BWi16X1FhZXo3aVQ2aG4xbmRZOXpxZjZfNlpfZ3hrZS1RM2Y2WjZ4LU16TTEtemJ3SjAtM3dNNEtNbXBJdm52ZTUwTTJ5TWFwcGdKdnI2SllFdGl3T2dEdHNmSzFhWFE0dkZldEhEcUZsQlpQOFU3WGlwNkdXZ0RxeFlQZXlxS2xnVllKdjd6b0xQN1pKbVJscy1iakNDR1RXNFIwMGs4UWZoUmh6LTFRRVA1XzlieTVHZUVhbVJWRmRaNXlaTHBqVVVoWmdvcEw5UF9BRTh3YUswRXp3NVFOSWJ0a2hpblF0RGNMYVBYNHhmUURUNWtXN1BVeE1oSm14UmpyYXJiemVTdXRmaFVSS0N4NE94QXVxb3VXTURYZGg0TURKS0t4Um45TnhKTG5JNms5VTM0elZXdWFTdno4ZTd1UjZNeldrZ1hPR2p4bkY2bU1lYjhja2pyX2hmRzVvbmllSk9FMjZsbEJySERWc19UVWtJYmpiV3hKQ2EwZURhNFgtaFhQeWlfd1U5cDdGQ0FNZVJBQmZmQnY1VkxXbkh3X21VWVdBbGZUaS1oSndNREZCc21TMXhpTl9mZnpRajNDdTR4S1lmVF9kRW91OFpmN1NVWmNycnEtQjFyZ0hPSE5SdXBYN0xrcXU2blJTblhFaXNKdnNzcFdGZERERzA2SWpXUWtMdkstWVUxSEVLRG9pYVFhSENKa3Q2M3ZzQVRHRG1zOGN6VWtSc1JoU1M1ZVkxTXVPVGJHM3JYSUlpVkg1WUtrNk1jQ1N1R3JXaVBEUHR5M2M0MUZ3Uk95ajBnM2QybG55YjNuVE5rT1pYNEQzdGJaYlVyVnZmcGJCb1JacTY0ZEYzSmZ6QWhDTXE4VGZWd3Vuc0x0VW04MlR2cEJhREhVaElZRm9RcU5QcF9ZNWNWWEdmZElGNVNmWDUycnMweHZrSmFUSnlkRjhfZkhCdkJUWDZxTEk2cGkyNkRPQlJQRl9YbmVleVlFNkxvMzlTR3dBRllJMHZkYUtxOXlqbzhWMlM4bURMU3hTQ090cTB4V0pJZ1N4b3dvVldONGlpNk5WZmFHU3ZLTUliOU1LVTdwTFNnYzNEUl8tbUUwMXhzT1pERXJvay1kUFJMTnc3bThfMV9vdmQ4NzF4MnFBeXNCbU04by1IVFNlbFZtd1lId0diSFdyOEluUkNXdUxHN2NoeG1UZXlxTE9fdlZIbC00V3lBdWQ4NXlBTjlySVZFNzBuRzRodGlPVFkxc0xRb0Foa1ZMVWdKQXVsQXBxRDZndk5fa0o2cHFlR2xwSk5iNnlKVjhXSjFscTQxaTJxVmFRdEZuVl91T2xXQU4tOURZOFJNTXFMTlp6UUN2YUJVZ09SSlRUbzkteklpckR0ZVpLbEJiekZoQUIwdUFWbDZlcXllaU5sdG5peU42NjZMbTJwSXppS1FmOWFYNlI0TndPMW04Tjc2V2V4UWFwOENaX3hfM05xNUk2Y2tWRUlpSEdrZFBjeV9DOEZ0bGU3QnI4LTF1QnpzeXNzZEpoUEFhc0k3M2VjeUFIRzhMTG11d1VwSGxZWkxjNUhOSmwxUkxpYWhLRHNLXzF5TGkzZ0JfUVRaaFg4cFNhMVI5QmxNMG9UNmdrSXNKOXAxWEN6X05Fa3ZubFdkeG9ObDBBZVZxUjhlc1NmSnVIYjA0Vms5VGl2b3FiaENYeVc5Z3ZzUlVnTlpMMndxVENrSkw0b2RDcmlteUlLUk9vZk81MW1mRC1RazdPZk5vb2RmRzhhaFhlcnBlTXRVdGgxWEc5NW9vR3ZsR1dra0pUbUdHVDV6Qks2QThTdXpXQmxmR092ZUI5QkhIMlJvUDlIWmxYUTNTdGtqLXRtMUpZR1BfSDlXSm1CQWNQUUs4LXRkMlp3MHhHdWFCdlZKT21GdXUwdHpZdk1qck1xb1pJUjVUWVNSNnpzNXJuVGxoT2lkNTA2bWVOYlFqMjdXNDgwV3o4Sk5FQzlVblBfbzdSX1prcVNHZlJRV09VOVFaMHRUV3FDY0cxbTVyZ3g0SXVPRk4zSmZuUkxUX3h6LWtZcC1TbzU4TTBNeEV0TFIxVWZ4M1M0ZTVUOUhKRllzOHZSbTFZXzM1LWx3WmhUaW13V3Rlc0VPblpUQ2x0bDFqMURmZE5wd2ZCWk9BOU9Fc3ZJWUJUUWNvX29wQ21JaXYtZXFhdzFkeVVTTTJoZ19NVWJTYW5MZVR6TWRJbDBpcDZLYUJXMG1oVk5oR0hWZV84dXg4TmZURFdmOVNDTUNOTms2SUFqMXhISDVNWlZKaWlfV3lneXlXanQtcjNIM2xiRlZ3clBQTkktVnVJblN3Wk1lX0xzVXZKbXVteUhwSU1wNTRnNzZlaTc4a1BDR0lCUFJlRlVJWjUyUktIWGh4c3ZGOE81Z0t4TEk4bmRmcVdJVVpjX2QwVlE4b29PYlBZWnRqV1ZWWWQzdWNDN3lhRFdrVWtKUV9RTHpjWmhZMnRDYzJKeHR2Q0VLVDU3blJEZW1zWGFnYjN3RTZPV0FqTXNJUTR5dVZhRUVybDVTTHlabHFEaWVLeVBkQTIwMVJrZGtkQnh2ckdIQ3dySU5XR2JRTEFfeVdPUFMweVlYLUhoWjVldXVwRy1fX05PeTVTaUllT2dKVFNXSkRST3ZaLWEzQVFnb3VsNTRuNWF0YTB5dU1PQ25BU3I2RGpibXV6Umc5TTdnTGFONnppZDFwb09CLXY1UllsYkhscnl1YVpnbFVfVDhlXzQ4a0dZREhESW9mOEJvQU9FLTR3cGVNaWhuVHU5bnNGbW4tOXIzR0ZraW9aUjBzb2ZmelNjSFRlTTFPSXhOblluOVg2OGtIYmMxNGFqWXJJek9yeng4NUc1UmpTaGVGXzd1OUFZRko1QUE1YW11UkhCbzlBaWstU05IY1NGSUVXN3VIQXJNbTVLSEtaSWxJZzM0cnVtYjBYSFY4SjAxb01CUkhXVk5Eb25Fa2NWeFRtZW1YXzl4OXdDQ19tZHpzU3BBRHpGRGFMVmV4RHB1azRVQV9QNzJCdkZuTEZPb1JxQUlMUVVKeWVFVFZMYlBXblI2cTMtQjRwRENwdW0wT2RXUTBqZVZTRjc3V09mR3FKaFJfWi04WDcyOU9UU2NrZW1taHF2YXdxbEJ4d2F3RDBSREFnYWJNY0h0Q1pKRXZzS1kyVjY2akdyVldQVENEUUdrRnRUTDV4Z1hoVU93cXRRTDAxa3hHZm03dWVlSkRlTTJiTHBEeFJVbGlUUy0xN1ctUGxlNGJsUTJQVHpBQjBranlFelNuQkY2alY3Y1NzZXQyQU1lWTA0M3NwdG80MUl1Zmo1d05BLWFva3gyV2RBODhiQ29RVzFyT09aZXczN1VtcjU4aDV4RDlaQzlXSHVhU1Jmc3ZjdF9oeTkyLXZURmJHdUZhWjl0ZUlJR1NHeVpBNEh1eW9vQzlKc1l6bVQwTl81dE5PMVAxY09XQ0ZsMWd3b2JrVXNpNHl6b3hBTHRETWd0NHk0S0N5TlFqQ05IdC0wSEZidk53R2dfcF9iTUhoaldFS2NIZWpvQjN6dnJZVFUwdHlWU0F3bWZYQ3BRY1NkN1RDZm1oQktERUhQeTlZUkNYYlYxSmhtQ2VGeWxDRjhJaTRYS0tnQjl4eGJXOXd3TnVPTlVDY2l1UU1BdElLUDVfWU9fU0k2azZGa09yNlpYUE43bGxSOGV3bU5Pb2Z6N1k0Q3hCSy1lNVY0bk02eUY4Q0xZMkNqbXE2ekNjczVwdWd6UDBheW9RVVRNZEJGaVo1YzJvbFYxajJlUkpCNE04UTZnQU02ZW1EWlQ5WDFNdzlPbXdMNTFLZEVwbGI4M25ueVM4c09MWHlMVEZJZDFFcUpUZnFzbXVsWUNQOFJjcUJCOXlzYjBTVjR3WENzdWpRTnRWY2p4aFlkOXdrMnB4S3NIZFlMcm94SVY1dE9oYVhudE80dEsza0pfQm9SY2FCWHVxOVkwQ3lTUFJQX2xCWnZ6V1lJNlJlMkphOF8yRUtGN3EtT3dNMGlheGMwaXRfZlh4S2x3M2N3OVFWNThFNHdlZ08wUzV1MTdCdzdOU0pyWHRnS1NnRmI0WXlISV95aldTQUhqN0g0VGpyS25zMHhOcVI5VDNhOU9oR0ViRkVuVFZoci03SWhDNHhhaUF4aE0xTDFtVklOSGt1dkkzVzQwcGxBLV9hLUdUdXZhME5aS3RoZTZ2cjlGNHdSLXNaMTF3TjJmeFc4SFBBUm9ndE5mT0RWeEc0RDNIbENTS2ZzdHhvRDA2ZERKSzU3QV9pX0VhQjZwSGZ5dXlqY2NhRkUwaWYwMVZXazVHYXkza25qRHJHa2N6dG13eHdmOWJoY2VZenpDUFdPM2dMc2lwS3gyelRFV2xhbFlqbGN3QThZWnFCdXdULUJyUDUzVEV5YlhCZ0FURG5kblBJY2hMLS1KM2x2UXhhOUhCZjNRQUNGLU9vMmxHbV9NemVBcmUzUC1OMFQwV0tOb2pzcWE2bFJOR2xVUGtHMXdBMGJPMmlKQk9LcmJNbVNGd1BzU1M0TG9lREhZWGV0bmJLVFlhRUY2TTNtNnR3dEt0N3lkYjhIUllGN2NfZjljUEtxNFgyUFJEc2N3enR3bE1BMzhNQTJwQlZ1aXFJd0xqSlBNeGJ5aS1fVHhYc2VoUzhUWXFkdi04WTh1eTZnYU1CNjZwQ2huVU5wTmNaWlh5bWVKclZ4cXNNdnZsMkR4eTBDNElvTDZQanc1RVBzZlFhZGJOcE9XZkNmM1l1SE45NDJGZzc4b25iT3BNNVlqd0F5OUFwN3d1V2k5WnhoV2N3SVNlcmU3TnduckZkUkJ6VXYzbEFtZ0hkNXpaSlc1NW9Wbk5KZG10Vmc2Y096cTVFVjRFX2ZYeGtCdmNjZm5aR0pfSlpXZ0lfNDROcDQzNTlFRnNOUUtlX1NydHdyYWRxWTBlejFsY3NPMFdmUHo2cjAzblZwR3RaQlhvTEpnVHAwN2dwR1pOcFBuaVFyeGwzTnpmdDc4SlVSSFFJZ3ZrZE1ZSnQzTWY2bWEzVUdrRndmYkFCOEU3WG15V0Q3VDVvY0JEOHlqMFpaNXNtTVVFaVpsVE9VbWNDSVhubE0xX09PMjFORmdsRWVSUjQtNHVzUDhma3dxdkJqQVNHN1MxTTNKaTJuU2c1Y19icWUtTDB3NWIxT05QR1NjcWYwWVhRVFJmYlpoazVpMHFTak94WlFDcVZHNVR3UmFjTFg2WHRRY2llU0ZXYjhiV0ZDcWY3VjhiZmd2SWpMQnlha3VzZkExdVl5ck1YeEh1alVWRFRTMkl0cEpiQXQ2TWNaeXhGNU9GeE1HekoyNmluOUJXblA2Vnd4YWNVVnQ0YUFyeGRKeHFrSW9KOHNNWVJ6eGtoQ21oNFEtcExudXZxUXNVUnRteng3Y2R6WWpkb2tRLXZ2T1ZIMms4WXBpOUY3OXZITFRDZmMyQ3RTRzVyNlRRMDdNSGg2OG95ajFJVkdoa2JBMXBwMmQxdERPcVBaUmNLREJMWTJPUUZoV09KMWpGUTd1ckdjaW1wX2JyQmpuaGJKaFRobHBMZUxrQWhYYkNYb3RnSE84S3cxa0pQdHh4TTdpS0pzUS1odnl5aHJFanhkaGFpUFRINjhxaTJGZmxLSmRMc2tFWHpCRnNUZkRmWXVGd1RsdHljaEN4eG4wVjUtSTd6a0JnY2JQdGl3TldfNnNySzhGUVFQeWFnX2V4NVJ4VlRLSmtOZXEtaFdKYlYteTRLWlVDdEIwSjl4UE1fVmtUMEQ4V3MxM05neUxrZEgzQ1daeUxmN2hULTdDeDlXd2ttdVFXZC1QWUdxZHBYRkZheFg4MFdoUHVCU0U4dHFmSHhXNGR3SkdmcGg5ejVNSU9VTkVxdmRPZmVJTV9WZkhjQnVzTVpzR3RDM180T2xIdXpvR3lHam5TTXQ1NU5tQ19NdGlrckxNeDlLY3RWWkQtUzVuQkROZDZ2OVJIcFFlQWlPWU9tdlZWcng5b3Q2ZGk5a1BoXzAxdGFEam1ZamFUbktFb3pheHphSWM3cTlreWI5R21LYlMtUlZobjQ5LTVITlNxOXNMdnR4YVhUc3lPTk56UTlNSHp5aGNrcFdVc0wweHphdzluMFR2VEhzNXdicEJzVzN3MVpkVEVhRFJaNXZXZHNRMFFrUklGWXR5X1RyaGlRMFlZbVFtR05Eb1ZOME01N0pkQUlmb1hRS09KRWg1R19vNC1sTUstYUM0X3BiOWEyRHlsSGtaU0FFU2pITlNubnBwbUdfMFViMlFCUktQR2N3bHBCT2xaRWJKYzRfbHVTWVhxWFljODduSDFPbWhLOWlHWEtWLTAxQlloczM4aTAyYjJlTHo3NS1lcnhsVFFEUDROQkt6LUZ4VnFnTWY2eDZBb3VaUnY1cVJpb1k3ZWxjeGRhVHJiMGY3cThlenRLd053LWsydGVBVzJXYmlNVHhFX091U2VPTVcwMXUzbS1EU1owSDJoVHc0OG9UUS1pWDJtNVlkem5mdlVkTVVkUF9pS1Y4NmR5TG1aZURTeGJyUUdSRGdyZHRyV3BvQlJTMDVJV1l5UjBhcGMwT0s0YTZVMENDWWdoZndfd1JINUctRWZKUERhS2tfZng2UjRZbkZjd0diTUdyVEQxeXF0UGQxeURwVWF1dUVUZGp4VEhrWFNfMnZ1ejZXZjlQN0JlcWdKdXJmZ012RHZMcDQ1QjQteDNyVEktZUxzcnNaRGdHOVY0TS1YUUhZZ3NabDlwVU5IQzFyVHg3MjNSQmlZaVZOcXlXVTBlTER4bEVnT1ZMMzNzLjgwY3p1OHVFWmY5aHdMNmhlNWdHOEE"}' headers: cache-control: - no-cache @@ -77,7 +130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -91,11 +144,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -113,12 +166,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/ada51ab3a12044228eb5220085ed7fd1","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rG7TKs70mQqtxO-Xbt3k3pXWYYotG3GRgJlc108EDEuZWjY-Itzbda_DXmOFbXOTdBl52m9DCHQOZx8RK8p98muUKF_Dpmh0S1QsmfV65_5ZtY-lOOoyCbQOhjh_AZxcJ0BQ8oMBUyBFOt1ljP33ABGZOtVYLZ29ghk1FNtTN5UK20nnCtNFbmDlmtcxjm9d44pwgtQlv8Qdwqv2yS-24qnU-VXpkW_1Uj2zIXBbgPXMoCIZxBDq5NPKiAczHKEBqu-As7EqTeuSkWtB52zzDaqCWD6yICbxiRDiPGf0g2hCaftFU0AtOOprQsnFTwqw9BYwitlehgY6ap3yoCkwpw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/e37af5aca9214f0da06fdcdf9b3673f0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3whdGIk7Kq6uXQsBCR_9LkLgcdBzTr_YUP7862Gqs9vc2d_dJcYw7yG48R37hir2x76m1FOETbmgJw-tKcxzNvGIDxOvM4xCPAvJhx3rLN96-sdUStIcVmbiQ3rLsiaRdjvwHfS4ZGamgHGFDBuupS6Qc-hFvERUOVNUDZMEOZ7HEON7HCD2_o7QUFef7JLIz9JYBBu1b2JU0hgfINkID7NGOhzIBvBRy0phEycM45Fjy8m3w2vn5bKKWEfsS1QmW3QUjX6pCY8UE0PgB0QJ5hJfYiZUPi7vot6I4tJlNmx29ngzJ7qMKbxxnTVxHjQjB1GLQtVBCp5pAaAM_GGv5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -127,7 +180,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -141,18 +194,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: !!python/unicode '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmZiT0tzUV9qSzZBbFlsbXhPWURScEpFUDFpaVRzMExtZlg0aHMwS3dyeGpVWEY1ZzRxcEFLbTNkWk1nLVB5WnR4NFRUcnhqWi1KOFJIRjdwLXNSbFlHbWhFclcxWGZsb0VPajhWQi1kcGo1eEQ3dzdrR21jVkhPeFAweG5UdEh2NGlKNUh6WG5WTkxQbE12MkZRLUJhdzlVVHZRSFBWcDJuZVN2OVZGTEJOUmpWNFo0Mk9lSkxSYy1rcHdDTzNhV1V0Yi14amdwVU4yU09DYkFVN2RmS3ZGYkRSbWkxbW5xbVIyelYxSVp0cm5jN1E3ZzFraS0zNkNYLUNCWC1yb21pOXN2WFJwN3phbFc0LTdXVEt3WjAzdkhPaWtrZVVJeEh4Z2pZellac2MtWkdwQWI3dUl6T2c2SlViT2xiZmFCSlN2THVNam85dFR3MXlQbWhWQUMzUS4tT0tQWm11ZXhkQndIS2VKY2xjVUpBLlZIRG9SZnBvMjZFMmNlY3dnaTA2RjZEc21tQXgtV2pwZTRCT1FMc1BteWd1Vkxucm5DT1RIay1EMTdNbjVzUnJqTHdIcUQ3X0lnMC1UaUZrRDJOUXRaZFptRWVILW9mWFdNNUVmM3FQSm1PcDVQZ2QyMVkzWno1SjNDWmpITE9Dc1ZISWotM2F4Z1dQQWdTVkpHNnNSWG1uZ1BPMndEd0I0MVdyTVRwV0ZuZVVPTlNJbWl0RktLWlVobHRTRDZEYzZVeUhqMmtnLXA1QXhEeC1MLUl6Z2dRbUdMSXlGWVhEZVRvenF3VWRRT1d2WUpUU1VMNEI5NnJHLW5SellHdlNwSFRiM0lReGJOWC04Nk53alNlbU4zcFFrTFRwSERYYl9ObjNrTVNQS0c2a1hHc2U5bXVXWlFmUWJoaFZBeVYyOTMyazJfX21ENzlQSXpiaFJQcXh0ejZmeU8ybkdwR2xmM3VPR1U4dkNrN3BTMGVndmI5dEM5N0Z5WThDTzBEMVNkQjBjaFdJNkw4X0htbnBFNl9LS0c4VGhqOTFwdVhuN291WWctQlI4ZnZOYVFENC1SU2VjNkJXZ0hRNEh4ZW9reXFnX1hndmhQMzVrcDE0RVJJTGNzMXRjdTdHTGUxOVhGUW5pR1poaEV2TzYxaTMyVmdTVXZYc09XN0owTVRUcE5HR3ItNlZEZl9ZaE00XzNXMmptSXlUXzAzSXhENWpUNXU1UHVsalJqYkdkRUVzaVNsRVZVR3RuNF9hLUxtdmtzRFBjM0xqZGk3T05XblFHRGdVVUpHdlIwVXpELWQ5MmVHZEEwUzlESUFaY1JYM0NZSVJtbDZSWmtwdG1LSHVfVnNvNzYtczU0YWpkMVBBdzdISVg3dTJUU0Q4S3BuM0dmMGZmRnkxemY3OTdjUTl1bVY2SVVFYnJLRGpvRjNSRG5CZldKTFRRSWowUV9SZzlPeTlkOGJIVXFROTBOSFpUa3M3LThFNDhuV1Uwa2p3Y09kV3dFRnAwVTF3RVhkWUV6QVJSSkZ1aGYyS3ZGRDZsTEpWRVYwUVZXQ0RXbzk0anV3bVN2dFRseXEyUDVZc0t6QU03ZEVfM3AyOXJJcDFNM2pNOHcwSUlpMzJTZHRMSXozYkJMdWtwcDVFNmhGZERKc2d5VVJldTVRQTh3d04xbmVWdHlnQmRmamI1Zk9ueFVMRDZ5ajg1cUxwWmwtNUJZTmwxQjFyOWhpamF6cTJlV19BSVY1NjU2RjkwV1ZaUGg2ZGlJeTRMaTVxaEdWUEphWFZYRFF1RkJmZEktVUpkQTNmZVZZSExqbmE1VjdUaXFLY3ZTSkpseG92WU5UYW91dkFLYnBSanJlRmM2aHlrWnJwN0tyMGQyYk9aaTB4bURPUVF3aW4wZi1iWGNVcXdaVkhvYUIyblJFQXhFU000cXRhenBreWdHemVQay1IeEpRRE5DSXV2MlV4RGN2UXpMQ21IQjkxYW5YU2dVY2NuZGsxNG1VRTBXYUJiS2owbHdjOUQ0UzhTZ0VDcFZ5a2l1c01meko1Mkp4XzBsVHZZMkFkUjUxbVlhS0RCWVZiazA3cUFVX191NVpNMWw5SkVuRWhaemZsWjJWNXBSQnJsR3A2MVc1R093ckZ1OGZRcjVHblc1TmV0WjFOa2g1N3liMVQ1MUZjcktTUTY2U1hXcTRxSGcwcTRDMkJydE42U0IxNHZsTk42LUY0dFUzSG5OTEFfWHJFTmNzbnU3MkVnb0NnNEwybXhNNklBTWQ0ZXhnUDBhRUlyQWEwdEJMUnVKVkVyajlSaWdwWEVfdHk3c0JPSEpVa2FTSUVGTDNJMGJ1eWVicGpoZk1hamtXTG82R1VmTUxlbEwyVUxMSDZtWlVBSW5weURBYWZ2VE1tZHItOG9Rb00wSGl4elJRSTF6MExmZXRwZFBpWmJVbm5OVUhJanRENEVESnExa01qc2JvU0lfbi04c3FjenZTUmo4aTlRalNRalpDS1pjOGY3TnRWRGFuYi10QzhiazVWZmRJRlBXRE53emVLWFRkeExRZmF6N0Jxd081MXljYmdpZHR4LW9hcFZjUmd0dmN2THIySWotcXFWeW1OazNxbVNzb1h4SVFyY3NaZU93aGpxZ1dtUVRrNmlXZnUzMkVMRXAwME9WQUJUWVVQd21qU2RUWVJuWEhsWlZsZDZpWXFiQUUxNm1IWFI1eUt1dG5RSmk4c3RUWVZQSExjY3IwWTB1dWdBYTlfS1BVNVVPVnVfM2VOSThtWVptQ2VrVENnOWJ4Wlp0MjJERTRPb1Y5X0FzZjFDLTlENll1NmxEYVVXVTgtckhDR2dzeFgtYzhndGg5ZlhmS2g3Y21ReVM1QlhxSnNLdzhaeDI0RmU4bWJ2UC15SlM4MVBGRnRYaWtHMGlWcVotc0ZHZTVBZ0M0TlpsWmNQc3Jnb2RIQjhNSC01aGFKNm9Rd2xKTFB2Q3Q5b3NUYjBhWVZjLXRtQWxTSS0yeFNLWjFQYW83Rm1UOF9sZGE5M2VNS1pPOUFfcDhDS0F0d09vN3JzTGZRSW9jZ2UtZGM2RS1jVm1xZnEwZzNwb3VpMnljSGNOdnhYR1FSMWRpVTEtNXZ0RnowVXE2aVBBeW1HY0Rya1I4VDBCY01fLUZMU09mMUZocEZ3U2lzUzg3OWctbG9wV3Z3bW02U0tuSUVsVU9NTWp3TE94NVhLS3RyVE04N2FJRWZDTDdKWTRyTnA3QW1CNXRTanotdUtJRlVwUHpfLVVCaUNWd1YtMTRvTk5KbjRmWU16VXpsZ0d2MzNPNlU4aDhqNWV3T3JHZ2VDMS1DT2xTclprdkIyeVd2Vmx2Z3Rhb0ZuSEsyU21rNE5Td0hIZjVaRHpHLWhrc0FOSGJSZnpTOERPY1hPLWkzWDVSVF9WX2NNNXhOMFlGRWZnSHFobDhPUlBjUzNHMURtTTU5dlktcURxTW84UGt0N0JEZHNhS2ZmeFpOSTNJbHp5S1l1cUF0UXduSXJoaUFNTXE0WEJzTWVfcEtCd05fRlgtcXozYk1JUWNtU1pUYWhHQzhHOXFJU3AtbW1WOWQtTFBpOC1FVkdHSWY3czQxY09qQ25VSzFjLXdoanJxUDRQUF9MZ1Q2RUJiSUwwcTQ1Z3lJMkU0OWVBNWJIakJqa0xHcDRfcExDbDNheE82QmRFNjNPMzVoMld0UEdyR1pzU0VobVJWSkdrV3hxTFFHVXloSUJZSGZVd1N6c3h0TkJPTGZrRDJ4UHVKak9zVFh1TDF3b2s5ZElfV1NKZzFwZlFuYy1HZEFJbUtFS05lWWVjWDVnUjVFODh2TDdnejZWRWRGZTYzRjItdVR1SWZRSkpCSXVnLU5yb21aZFdqWkxUMVI5bkhCbGxKSU8zZTQ2eEpjN0dzMVpKLU42aC1JYmtobVQwRThYeDd3a1A1SlRwYjktdmZITDlab00wc1k1N3Fjem44WVFzSWNfY2NfQklJU1RFRmZTNVpQWWU0N2s5b2FMaWZfVTdGODJYd2lwaHlVME90bURvRE9jV0pCRGZBcUMtQkxIVnJjWFhHa2dscmlCMDlJWXhnN01zVTJGZTNXSFI3VWlqUV83WGoxeTZwUUNONmVNVnB1TEQ2aGVMRVBLUUp5ZHJkcktDSjVUa3YzeFBqOE5sVGNGWVpvQmMxX2dsbFA1YmM0ZllUMTkycVNmOHhobTlXTElWS2F5SVNzQUdZNjBkYzk4bHdycnoyNi1yd2tqY0Vadzl0OHNPYzRRVjd2WnV4cklOS1BsbGU1bDF2ZHNaMTZ2QzNmNWZfeVZnbE9PT0lEakRfRTVYRG9iQ1oxd2gzRFQ1ejNaYXdpejhXUlh6cGNHZDZRX0dtWkdPYS1TejMzd1VSS0dHbC00VFNaamtsMnJVRExYbTRlNmFENXpnb1ozY0laVzVRSGdYWmVveExRZC1YZDBOcWMtZVFnV3hTRDJwcDI5VDNyTGZjMC0ybWhYQTZhRFRjRFhVcThDdEZyM2N0MGJVRHluN0FhNTA2YWhuTVVzU3h3WG9WMkZYNjJ3bFdaQ2VwUEloSTRRRDh2MHZUeUgwNFI1alhTcEtwdUJmWkZsRi1xNndHSXREOUMtdDJkMnJRbVZVY0NnclF0dXBkRldQclh2b0ZXc0gydUlPMzVOZmZ5aTU2d2ZtVWIwUW5ib3NRODNwSTdRZVptVVNDbVNnc1RCUWJWak1tYVRvNVA0SDY5R0l0VHpXa1k4Y25EbGJ3Yy03QmRGcnUzM1BDck9ya1RFa08xa1JtaVVOLW5WdnRmZEpiYnJsR01EdVRmWGpFVzZPVnFJYzg1cWdpZVFvY0tCajdIekl0TjNfTlV2OTF6dkMteU9HRUswbHUwMzdKU0d3TnhNRnI4QkpGM3YyYVBxMWxabVd2ejNCbXVKaEV3UUxadUNkZ3UtN2hWMGNsS3J0V19sOGpIVU8ySkVwV2RvUjY5ZkNEVjYwMU1XaHdkZDhhbVppZTZxdkU0Q1NwTXhSTHRUTnNhRE9fNXlJeVdseThYeXZmXzJ1T2Q5TmJuTHc4VGNoZTAzajlZUHJqa1pWaThJUThabFFWQkkxTl83T3NfcE9PTkdxVDRUSFNJcXF4NkpNeldXbG44REtJMGxMQkVWTmlsTXBPa051b253bExMNEpjd1VQczN5dG5EMFJleGxaUy1rVzM0RWxPWTNpMnR0Q1htT0lqQlV3MGRya1dtU21KcU1TRWsybUNKRXdHYVJ5ZlNHNkJPQlppU0dqRER5UkVlYmpwanJrSTFQTUczenZFaU45TzlRUjh5UGtMcUNwSnQxbHJQYl9LVDBqVnRXVWtSbkhqQ2xyelZoZE8xalIzSmxUcGIyTGs2S1JzajJUVktQOVoxOXpNdUFIZFZfWTdLYlNmLWNBSU5ITmZSNm9QSEY2VjVmaHNBOW8wT3RZcEJSRkd6RDk0d0h0Znl1UU9CckdibVBDUTNXakdzX1I1MVpJT01aWlJ2N3hXVHdzTUZNVTRselRDX2xmRnRkMDRKU0I5bGlFUEhwZ3lkdU9yS1JSclU5TEdoNmV3Q3NkZjdHaUN5aU9Kd1MyczdhenRDSzJUR0NDa29RYlpPYkpNOHhtOXR4eG8ySHdHeWY1S2ZReGFqWVF6WDJTRGVraUZnVUdhMzUxd1FJMC0zU2FkUlRFbFJfLVMzdEVON3RvZm9uVm44bER2dFNIellBUmxiWGpSRHR0M2ZCTERReW1VQ01vY0lpaHl6d1hJTXNjQ3hudGNyd19zeDZQcHhYS2pTZHdwTmUwMGFCaGhteFhNRi1LYjlKdWtGM09tV3FjTm53VlZKNTZmVTRwR0NPLXU2eEljVUtJa2ViSTY2SXZWeDhXQ01LeTRTOUtBdEhKeXc2dXh1WExFdTZOSXowc3l3MVFNeERPRVhraUh4SFhWc2RwN0loN2ZOcmh6bkpldHVmenNwLUd6eGJfWUVBTS03M3l1MTFoU05CbXNMek5FVnFaZXlLeUdxbnp0VUIyRlNxQzJZbm9sTWVURUdDRV81a3lfenhocXNmUFltck56RE9LOWpuOWQzRjh1dGxJM1FRbk9EcEdpV1h6MFlXdm4zdGZKelU0VlkzVEJHbEw0YnNzaDhiQjZ3T21zU0wzUGFCeVhWcWFpNXN0WWVtNlI0Um5QXzZLRWFwRkhWdThkOXFrMVZvYS1VRVZXWklBWWZEYVl4TmlMU3JLYmtTUFVJNFBPQUE1RnczM2owUE5fRFdlUmdQalFVVWJENXU3NlotVE1XaGlpenJqSU5wcTRuSVBMUzhhN3gwdGJ6LTRrYzluNTFjaE5iQ0kybUoyV09EVUk5dTBUREZxSDk4cE9xUW4zdkpINjZiTnJLS1BYcmpwbGZscnJaaFlhZlhqZmVpenBvb1JHSnNFTU4wWnFwck9xc3cxanpKak1qeE1aQjNCLWl6d2VYeW9tVVJ0VDNFOExoYTZFTllmUGEwWGoxb0ZuTENyeTJvdlE4amFkbkRyMnVIdUxEWnlyQUdVbi1kb0lmQzVRN3VQWGY2alNESzU2TE83b2JjQ0JzeW9WUWN2UlA4T2R1R2hQbF96d1Z2ekpIb1N0RTNDRV83b0ViRUlvMGtMaWY3ZEduVWlRQjczd0JhMGtBaVZLc2FtcElJRFhuVDQwX3hhVTgtQ292aFQ4ZHNxeW1iWkxVdkRXbWI0VXUzYS1fWkI0U1V5WVFlZnpZOGxPdE9zOVltdGwwU29BZzZLOTVyaTBncVhGeXlXY2NiTmF2U1hqX1NxdU43RzVyM0xodXFrOVJGYlJxOUtBX1BaZEU3VEVRYmt1dUFQcWpEenJLNjloQzR2NGFzMzEyRDZ2bmFlemNJVXJwT29za2gtVkRERkVsZEdidkhjTWF4X1dyejZkaXZiMWxCTURzdGl3NnV5cVpsOVo3YmZlUnBsRHJjZTNqdEtkN3dicHF6and5by1VMHdnOXJCc3EycjZka2M1ZXh3NGF2RDUwZ0xuV3lNdHdOLUs4LVB4aEt6SmpabWVjNGxjd3FPZktKOXduemV4dnRpTllDZWxCOVZLNjQ0OVZld0xFNHRtMUpUMktrQnlQdlJQeXNsVnlFVlhFV0hEYkJuX1ZTMmZ2enR2dEl2cTBDMHZYVVF5aXZ2TXhsbUNhWGZtOXRJbWNkeDVoSlIydzBDeUwyVHdXRmF0Y1paMWtndWFseTlfd3F3VU9uN3Zqa0RDZDVyLVhlNU9Ienc1alFfXzBYRWZGT0JOekJ0bkR0RHFvZG9YNURvb1RsM2JqVUlOTTZWSXNtUzZGaHJHZ05mRTVTR1V6Z1JPU1hjaFlzOHlLb0xVZm9NY1U1Qy1OVFZrdnE4a0lvNW1FSWlGZkpUN0JWLVlxTm1aMFRyajlMSDdUZjRVLV9aSW5EazRSTGwxMnY0anAwOFlCa19hSy1HRDNKRFFmTjVjaEdVUFAtVF9iODJaM0xaZENQSGp5UTF5ZXNJbm1qZzRia2NJSjR6THlqQ2RBREJ1Tl9sLUJqQ3M5bzAyblYxOU9KWWRUNlhFTTc3cGFOZ0pST090Y1pWeHNHcVlTb2liQV9kc3FycnhqVFVqLU9FZjVQUEY3ekVnMU5lQV9qems5cDV6azFSLV85NkNRLWtSRmZGcEJ3enJ0SDBFTGtvS0h0aXliR1ZfYTVYaFRfZmV3SC1xYWlsTHRlUW9MVDhSeDlJQnR6anZiVElIM211QlZlNHhHMHFMTHZTYlVkLXlIZVYtVll1ZUZTajFWa2Z3S1RBYWtSbXZhcDhzX01Zd2hiZ0VWalRiX3pJZnJwcXJxUjBBX29BcEE5Sk5YbzlrVk5PQlFRbmhRb2tnNTkxS0hFcEx3bUQtM3E3UXVuWVZDM2ZGM2NYcDhRaFI3RVdmU2xDX01ISGtwYXN3ZzhEdFgxQmxzLXRWZWNiTU9ET0xleE9TaWlLbmdnQTZnX0tXS2hCRXVvNWhLZ3A1N1lobTVvamlkU0xzWnh5X19zU0I5cm9nV3N1S3h4dU1hRjNyd3RlUFU3QVQzUlVoSXZtMDRMYmQ2OGhwTHhfYkM4MldJSFYyc3ZCSkh1b0pPYUJTOXpvQ3h4YTYtZUlla2RQNEtmeEIySnhQcHZ0QVdZOWRybC1XQW5DeldKUEMxS3NMaXppcEFPSGllbVNfRWxKS3dQaUViUDhocXpYTDNsQlp2cWhtTHhxU2NwMUZ1R0QyWWlYWTE4RmN0VTNPdFhicmp3WjNaZG9aUjBaRnN0STcxZmluYmMxemRGam5Ecng4STRDcms5M0YzdkI3VmFKNWRDeEtCM1Rnb3V3SVVOd0ZsMEJqc3RuSVRpeTZnYWVZVC1jMzcySmlpNzJQbFFqU2R4UV9rUjRHb2VFR3pYdFpTSlRoNEVRS1BvOVhmbVR2ZEltTHp6UkRtSW02cWFZTDU3UkNWd0JXaTZlY1pzay1BMmRsaFdaSWVuczhIUkkxWkhQSEk0WW1vXzJia1hmbUNrWE9uWGNUaDFROHBPUUtUTUZhcFlCc0E2SEs5bmdWUVV5M3RiaEdTVVNvaWxNemFGTzVEZ1JMZmZxNGg1aWtTREV5VkFGOTVfZ2M4NHlUQXdtclktZlBkMmxtTC1IRFJLNDl1UThTZXhlSnFJZE9qTVBNYmc1cjc3dXVqMWpBQzVnOGx1TVZfZWlpQ1E1b0twYlhqanBBdHhFZTFJSjN6QVFYQXhpaUFPMHF6ZU5wRWFLV1F2bGpUSXRQcDNlWENabG9ZdGVicEJVM0VzMmtmYlFKaHVGMGtSaWxKOGlkS2JhSHZvSmxKQkJnaFZpRmFsQnM1TXVBUGxwWlVRbUZYMU92b1UtdUdPNTdURnI4TVA2cFZXR3pNV0ljLTJnd3RuSW94UEZsS2lZaDdwcVJPbG1meEpyMzh3LUxHT3F5YnRLRk9WMy1ra0pOejVTZVNrX0xubXNxUzdHOGZFUnVKTjZ0N2p0WjBMLTJWVVFWekE3c2NpamR3OGJST19CLW85MTMtQW1DMmtXb1NKY2Y1VWVlRjh0VU9FenMzMDFINlUzbG1sWUVtcmdZamJKNFJCQXVDcjY0a2N1YnhyT0hRR3dzblIyWUo3TVZQaTQ5RUFNYVVON0JkNk1INFkzTUR5VTc3aWJKcG9adFY0REVmcEEwSkZ5b1Q5QjhpR05tMTFkRXhuZTFhQy1vRXNoeGRoMDJmRXVJWmtNWVRkYnRTeTdBMThOdDVHRllFSE9PY0dnZHh6SDduSURGM29DeWJnVWk5NmxxNWtCSkJ3Y3Z3ZHRjRTM2dWtnOGdfeDAtTGdIc0o1Y3BuM3hjX0s2eWNodzFQajBIWl9VRnh2SW1DaF9mTGVCalJBdHRnLVQ5LTBSc1F2UEp1Nm5fVGEtMG14bzhDUWdEanNBV2xhaVlNTjhxTWtrWnVNZEhxTUlITW5sZndyWE1kSmJDWkNZR1U4dGhMUjkwYWtpS1VzVEJtcGZtemdTQUdVcXpOZjIycUxaaGdmdXBkMWFqWjBGU19vSWtoQ2x0WC1SbVNmWnc3RVV6Y21Kb1l4bmI2Q2dqR09mN1pfbENfMlY3NnNkQ0ZUZU50cE1zVmZQUkFnaDZRd2VKY1cwVURlbGpxc0RXM2FrWGpDZzdwSlFmUnJLOW0wbzRnMmFXNy1faU5yb1hVSTZmYVNqTFpnb0FNWm1GTUxsZjh0QS1OblRmYXcwTUM1NUUxTGRxeFVESDN4MUNFcmVaejN3NEl4cVVSbmlPRl9BczdrUlpSa3h4a3FfR3NmT3BKQzNwaE5FNUxWeDNEMHdwaTBBUXVmNzk2LVljSXdPemd6SmNoSW52QmxfdVJUejhQNHUyWUhjRDhJVEl0ejdWaEFQbDRWMTZjSERFXzA4TjZicnRoQ2ZWXzh5N2RudVJTLUJfZVV2ZXIwRl9fc19pVVhRWGRHOTh6YV96TllZTjRPZ0R5NjFiZS1GNi1qTFQ3VVFsQVBoQWNNZUw1clZnS1E2cU9QTEVVa25fb21VNUNBUXNxTk15VmpCcDYzUTVYclhyUW1neFlGdUpwOWkzR1NQeHY2Q21JVTMzSVRVWWJFMEI2UTBSbG9DSG4yaUlxcmd0MGppUEhtTk1DSS1uZHBlTUQ3dVJpVDczOVFweDlub2Z3UDdsalByazRIRFZFVTV2blBhVXBvd3hOeW83ckFPV216dGJoc2ZTaXVRSDBDdzltRVYtcW85aWlqaDg5LWdaZXVIaXdLblNGNjRZT0ZxSDgxVXRPOUFScllUWmhGNG1tRmVadkxVb3RwWlI5c25TRWw5WXhhRFJBeEFhbTdLWGFJbmNlUkNBYUtDUGt3UzFRajhsMTk5TmswNmtRLklqZWR3RHYyTEFXenVBeUhmUlZkblE"}' + body: !!python/unicode '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLml4RXd6WGRoSEtUT3BaMVY1Qy10aEtCU1NTVUdEQlBEcnQwRWVmUjhyNkVPdmJyM3lFYjdyUEYxUjRWa1B2QWNQUUFob0hWMkhydDZXY2Q4WDhxd2NzUW0wektnR3BKbTFkRmFGV0hvLUlDSC1SSlZiR0RzYzZTdGlwaDAwdUtqUkZTdFpqOGo1d3ppR0FrVkRQSWFKTWRsajR1VTZBdnFNZ1dhQmtGcUhjdXF3bE5VTE1mQWFBaVU4M0xGMm81RjV1MXVzS210ajFfUUhJNHhrT0VLYmpfc2dvZnZpVXFIVkNNOGdOR0hYQzhjYU5zMnlTRUplRnBOMHk3Sjl0dV9CVFZfRDlKdV95djJKb0VyWW5NUG1ULU4xTWpjQ1RMTm44Y051UzNqNGJmbVgzR2I0VDAtZUJTWVR0dGxLanE3UE84QTRpekx4S1ZJRVBJSUVnd05jZy5zcEpPak5XYXQ5RXNxejZKcVJkeWp3Lnk1OGt2THRoRnZSVnFma0J3NXEwZVFjSGp0SHJza21mclNZQXJ6MnpXR3dJajN3eGhoVGM0UG5SanVhV1plZFlOcGYyYkZNVk1ZNGgteHA5LWhKVVVReldRY0NkanV1TWUxMjNRRHFPUGJ2dGFBeHI2SG9LY2lKSk93X1BHTWpBRjZiOUFYUU1FaHpBLXBMR1VudkhqTTN6VDVYTG9zY01mZHRwOTNmTEIyWVJaN3J5TUFKVkRuOTVJNzJ0Rm0tLXN3LWtqQmM1YXhDMk5YQktoalZrTDB5Z3Bxa3dGX0Q2LXEtYWw5eFE2WHFyNHlmS1EzLTBTUUhOa3RSc1Itc2VDU2x4d2s2a2tnelBobUdaeF9tVktrRmw0Ykx2NmtwVml3YTZHdlRlQzdvTUFPYjBkMGdvQ1gyTkxvakN4dEsxdDRxallBQkt2NEpxX2VYLVlaU3FGUnpFVmF4NEhWWjlRMlRicEMyZ21Gbkpza21GdDVrSHpIRk5LVF90eWF1b1loNlRvVlJKbEYzNzItX2pUU3pfX3kwejB3YzZxQmhnLXowZzlQQ3NmTy1ybFdkVkJpUENabmZxdDhOZjA2RXFQSnRucnlWZHlabmxpdkJwV3FfRU8zbjBlVktwTU1MNWI5WlZLZ2thcnctUUxiNFhGWjhjdGtjY2xLOFh6OUVEZzZvV1JNdm9UYTlINXFkRy0ya29Rc0ptSGFCZ0NzMFlXbVAxR3hZMFhzT3lFNzlvaUYtN3JZSUx5WG02ZFBSU3lsdmRnd2VGY05uTUtYX2M0N1otMGVvRVdmeHp1RlpMUDh4RDdsVHcxb1VrUlh0LTVJUld3bkR5Q215UzdzT0c3WWU2eDA1b19uTDhZZVNBNUhJQ0dTdS0tTWRBTEJrWXVHRHFQNW1JQUNuU2ljMnNjal9zdklVSGZIVzBla2ozeEdUMGNBVUxBTlk5cThFSTZyY3Q1NVozcll4RE4tUjRYTTdJalVCdl8xdlkxQVlYVnVSVUJya3UwTlpnbHQ5SEdSclZCNkh6TmRJbm9VZWVCNnlMRmFFOGVjTFFXdmYwbXh5RGFibEI5ZnM2R21LZUFxTlhyTDdqZUJKWDFpVEZyMGdDc202dV9VZGpyWE56ZjNLQWhzQks5OTFOSXdOTkhKQml4b1FOVmVxdm14ZjdqQjcxbVdtWElBSkVzZzFfSFd1R0QwM3lGTlYxZjFtMnlmZlZFYUVLUlByandhbWw4eW5kSk1NU2UyUV8yNmlXU2NaVzRsQnVGaGQtRVJDUjIxdnhvUUhwb0kxRTg1bDJBck1fUUZZaktHYjI4c1NOeW1hUy10MmVidXIzcHBScXptVm9Sb2gtSEZYR3N2aHAyODdXNFE1TnhpQS11bXp3TTNqRTV1cnpJZWlBd0NkdUM0MjBTRkx2dFhtLUdHdkd0V2lMRmRQTWNFMVNyeUdQS3B5RENFSlVXU05XTE16Rl9SeGppTmdSSzk3SlBfUUlac21BZFZtWi1zUXJXbE9XV19NTTRTZERKaW12TlZzc3RaN0dRN29tdUlTOUk5Z2NyZHNrQkNPMl9WMnJBOFZ4aXprQnQ0OE5Pakl5NEd6aWNSV3RIdkhLdTRoNVpTMGNMM2ZzRXF4c19lVkhWMEdSWF9ob1BQRmlpVlVaTFZqb295dkRwZ19IWXN2Q0hzQi1MZkVKLWlDall3OXVEcko2aWJ4a19wN0V5UmUyUWFMdWRWLXdJYS1UMU5wTEdHSUFqNjJydHNrUmItSTVxTzZzSFEzT2VZQ2VEUm9JYzdBVUttMzdTZ0l6ODhXOE5HUFBZSDBGNXJ5N2xQNUZZMFNSdFIxZ3k5cFRPbjZSR2J6M1JzWWR1QWZ2Q2V5YTBRX3N6WEwwM1g1SDFabE5xeGtYYUJUelgxR3pqWjdJZkFwaGJ5R0VmXy0xcXY5TFlqb2NXVnhodHJwRHUweTJBYnpEN21lcHZNNkoxYnFVbGp2UUpfQk1ueWtEVXBnZWZ2NjY0RmhhaWpYNVJkM3JKMHh5azVUaVltRzd1cFh1aE1lbC10RG0yOW8xeDI1RGI2SVhwZDNRNi1uNXpzektSQXZJMy15aERCQVRQcWpWNFk3V0I4LU91OVgzLWJTc282OUFETUxpLTVyRWZvWWhjM2g3UlJpcVBmNi1kR0hBSjdRaG9qVGROWklzX3NKN2FQT0s4cGtNcVg5SE9qb2tiNzZsQTJHZldCMDh6a3p3a21SSHJSOFp1TlBJR2FiSmNNYWloaHhUVlV3a2JRd0VpTVFrREZKV1VaQzZ2c19wSjVzV2ZmR0ltRmtGR3cxSXBnVXFMWWtpdVI0a0RwX0F0Rkt2Y2c4THBNbnQ0UEJlcnd6WC11QUpGelVNbGR4RFdvLXpDRUhpaGRyOG1mbW9yWHJMOXVPSmFWcDlvUWRXQVVJdXU5WVZwbGF4R1g2eFFqMXF5NFRFTzdwQ3dSdWFsMFZwdFpqMmN1MmRiX0sycWNnV0o2Z2xnQndhajRPT0dfeGtEUlNyRW1yeHpfNVdoM1YxODFISmVlNE03dkdoS01saDFWaUpIZVV3a3VCRTFWcDdLWUdPMUJSSnZpYjV1NjFIMlo1SHJwUGhBMTJKTXpwT3lrR0l4MUpGMHo3aXFpTVQ0cnJ6SDR5eXI1bThSZzIyaUtEaGtQajZObS1BVmw2VnlRRjg4MTVObGRUSTZpSmNoTGd6V1lRQzVrbXhpcUwxWWVQdjh0b3lfWG91ZjNRbUxSWERMM2FPZ1JQcVpDMGR2NGUtMEt6bnIwZUNSWm13Ql95X1ptMVQ4andJUWhDVHBRQkRRQ2pzcWN2NWk0R2ZLVmtVb244SnB3UVBEaUhaRVRBSVlrNGJMMGthaDM5NjlXcFJNUGJWZ1ZmTDUwc2RKNWdpNG5uMkZCaFlpQmJmYnlzS01tdU9aR3UxUTVuR2s5NkxUYU1Ob1ZXczNiVGZVcmZGcGJNVHFNb3RzMFlNVzdaR1g2QkZVQVJRZ0lRUllVVXRBeE9ncmtIOG9BVXFVRDQwYXZPX25BWi16X1FhZXo3aVQ2aG4xbmRZOXpxZjZfNlpfZ3hrZS1RM2Y2WjZ4LU16TTEtemJ3SjAtM3dNNEtNbXBJdm52ZTUwTTJ5TWFwcGdKdnI2SllFdGl3T2dEdHNmSzFhWFE0dkZldEhEcUZsQlpQOFU3WGlwNkdXZ0RxeFlQZXlxS2xnVllKdjd6b0xQN1pKbVJscy1iakNDR1RXNFIwMGs4UWZoUmh6LTFRRVA1XzlieTVHZUVhbVJWRmRaNXlaTHBqVVVoWmdvcEw5UF9BRTh3YUswRXp3NVFOSWJ0a2hpblF0RGNMYVBYNHhmUURUNWtXN1BVeE1oSm14UmpyYXJiemVTdXRmaFVSS0N4NE94QXVxb3VXTURYZGg0TURKS0t4Um45TnhKTG5JNms5VTM0elZXdWFTdno4ZTd1UjZNeldrZ1hPR2p4bkY2bU1lYjhja2pyX2hmRzVvbmllSk9FMjZsbEJySERWc19UVWtJYmpiV3hKQ2EwZURhNFgtaFhQeWlfd1U5cDdGQ0FNZVJBQmZmQnY1VkxXbkh3X21VWVdBbGZUaS1oSndNREZCc21TMXhpTl9mZnpRajNDdTR4S1lmVF9kRW91OFpmN1NVWmNycnEtQjFyZ0hPSE5SdXBYN0xrcXU2blJTblhFaXNKdnNzcFdGZERERzA2SWpXUWtMdkstWVUxSEVLRG9pYVFhSENKa3Q2M3ZzQVRHRG1zOGN6VWtSc1JoU1M1ZVkxTXVPVGJHM3JYSUlpVkg1WUtrNk1jQ1N1R3JXaVBEUHR5M2M0MUZ3Uk95ajBnM2QybG55YjNuVE5rT1pYNEQzdGJaYlVyVnZmcGJCb1JacTY0ZEYzSmZ6QWhDTXE4VGZWd3Vuc0x0VW04MlR2cEJhREhVaElZRm9RcU5QcF9ZNWNWWEdmZElGNVNmWDUycnMweHZrSmFUSnlkRjhfZkhCdkJUWDZxTEk2cGkyNkRPQlJQRl9YbmVleVlFNkxvMzlTR3dBRllJMHZkYUtxOXlqbzhWMlM4bURMU3hTQ090cTB4V0pJZ1N4b3dvVldONGlpNk5WZmFHU3ZLTUliOU1LVTdwTFNnYzNEUl8tbUUwMXhzT1pERXJvay1kUFJMTnc3bThfMV9vdmQ4NzF4MnFBeXNCbU04by1IVFNlbFZtd1lId0diSFdyOEluUkNXdUxHN2NoeG1UZXlxTE9fdlZIbC00V3lBdWQ4NXlBTjlySVZFNzBuRzRodGlPVFkxc0xRb0Foa1ZMVWdKQXVsQXBxRDZndk5fa0o2cHFlR2xwSk5iNnlKVjhXSjFscTQxaTJxVmFRdEZuVl91T2xXQU4tOURZOFJNTXFMTlp6UUN2YUJVZ09SSlRUbzkteklpckR0ZVpLbEJiekZoQUIwdUFWbDZlcXllaU5sdG5peU42NjZMbTJwSXppS1FmOWFYNlI0TndPMW04Tjc2V2V4UWFwOENaX3hfM05xNUk2Y2tWRUlpSEdrZFBjeV9DOEZ0bGU3QnI4LTF1QnpzeXNzZEpoUEFhc0k3M2VjeUFIRzhMTG11d1VwSGxZWkxjNUhOSmwxUkxpYWhLRHNLXzF5TGkzZ0JfUVRaaFg4cFNhMVI5QmxNMG9UNmdrSXNKOXAxWEN6X05Fa3ZubFdkeG9ObDBBZVZxUjhlc1NmSnVIYjA0Vms5VGl2b3FiaENYeVc5Z3ZzUlVnTlpMMndxVENrSkw0b2RDcmlteUlLUk9vZk81MW1mRC1RazdPZk5vb2RmRzhhaFhlcnBlTXRVdGgxWEc5NW9vR3ZsR1dra0pUbUdHVDV6Qks2QThTdXpXQmxmR092ZUI5QkhIMlJvUDlIWmxYUTNTdGtqLXRtMUpZR1BfSDlXSm1CQWNQUUs4LXRkMlp3MHhHdWFCdlZKT21GdXUwdHpZdk1qck1xb1pJUjVUWVNSNnpzNXJuVGxoT2lkNTA2bWVOYlFqMjdXNDgwV3o4Sk5FQzlVblBfbzdSX1prcVNHZlJRV09VOVFaMHRUV3FDY0cxbTVyZ3g0SXVPRk4zSmZuUkxUX3h6LWtZcC1TbzU4TTBNeEV0TFIxVWZ4M1M0ZTVUOUhKRllzOHZSbTFZXzM1LWx3WmhUaW13V3Rlc0VPblpUQ2x0bDFqMURmZE5wd2ZCWk9BOU9Fc3ZJWUJUUWNvX29wQ21JaXYtZXFhdzFkeVVTTTJoZ19NVWJTYW5MZVR6TWRJbDBpcDZLYUJXMG1oVk5oR0hWZV84dXg4TmZURFdmOVNDTUNOTms2SUFqMXhISDVNWlZKaWlfV3lneXlXanQtcjNIM2xiRlZ3clBQTkktVnVJblN3Wk1lX0xzVXZKbXVteUhwSU1wNTRnNzZlaTc4a1BDR0lCUFJlRlVJWjUyUktIWGh4c3ZGOE81Z0t4TEk4bmRmcVdJVVpjX2QwVlE4b29PYlBZWnRqV1ZWWWQzdWNDN3lhRFdrVWtKUV9RTHpjWmhZMnRDYzJKeHR2Q0VLVDU3blJEZW1zWGFnYjN3RTZPV0FqTXNJUTR5dVZhRUVybDVTTHlabHFEaWVLeVBkQTIwMVJrZGtkQnh2ckdIQ3dySU5XR2JRTEFfeVdPUFMweVlYLUhoWjVldXVwRy1fX05PeTVTaUllT2dKVFNXSkRST3ZaLWEzQVFnb3VsNTRuNWF0YTB5dU1PQ25BU3I2RGpibXV6Umc5TTdnTGFONnppZDFwb09CLXY1UllsYkhscnl1YVpnbFVfVDhlXzQ4a0dZREhESW9mOEJvQU9FLTR3cGVNaWhuVHU5bnNGbW4tOXIzR0ZraW9aUjBzb2ZmelNjSFRlTTFPSXhOblluOVg2OGtIYmMxNGFqWXJJek9yeng4NUc1UmpTaGVGXzd1OUFZRko1QUE1YW11UkhCbzlBaWstU05IY1NGSUVXN3VIQXJNbTVLSEtaSWxJZzM0cnVtYjBYSFY4SjAxb01CUkhXVk5Eb25Fa2NWeFRtZW1YXzl4OXdDQ19tZHpzU3BBRHpGRGFMVmV4RHB1azRVQV9QNzJCdkZuTEZPb1JxQUlMUVVKeWVFVFZMYlBXblI2cTMtQjRwRENwdW0wT2RXUTBqZVZTRjc3V09mR3FKaFJfWi04WDcyOU9UU2NrZW1taHF2YXdxbEJ4d2F3RDBSREFnYWJNY0h0Q1pKRXZzS1kyVjY2akdyVldQVENEUUdrRnRUTDV4Z1hoVU93cXRRTDAxa3hHZm03dWVlSkRlTTJiTHBEeFJVbGlUUy0xN1ctUGxlNGJsUTJQVHpBQjBranlFelNuQkY2alY3Y1NzZXQyQU1lWTA0M3NwdG80MUl1Zmo1d05BLWFva3gyV2RBODhiQ29RVzFyT09aZXczN1VtcjU4aDV4RDlaQzlXSHVhU1Jmc3ZjdF9oeTkyLXZURmJHdUZhWjl0ZUlJR1NHeVpBNEh1eW9vQzlKc1l6bVQwTl81dE5PMVAxY09XQ0ZsMWd3b2JrVXNpNHl6b3hBTHRETWd0NHk0S0N5TlFqQ05IdC0wSEZidk53R2dfcF9iTUhoaldFS2NIZWpvQjN6dnJZVFUwdHlWU0F3bWZYQ3BRY1NkN1RDZm1oQktERUhQeTlZUkNYYlYxSmhtQ2VGeWxDRjhJaTRYS0tnQjl4eGJXOXd3TnVPTlVDY2l1UU1BdElLUDVfWU9fU0k2azZGa09yNlpYUE43bGxSOGV3bU5Pb2Z6N1k0Q3hCSy1lNVY0bk02eUY4Q0xZMkNqbXE2ekNjczVwdWd6UDBheW9RVVRNZEJGaVo1YzJvbFYxajJlUkpCNE04UTZnQU02ZW1EWlQ5WDFNdzlPbXdMNTFLZEVwbGI4M25ueVM4c09MWHlMVEZJZDFFcUpUZnFzbXVsWUNQOFJjcUJCOXlzYjBTVjR3WENzdWpRTnRWY2p4aFlkOXdrMnB4S3NIZFlMcm94SVY1dE9oYVhudE80dEsza0pfQm9SY2FCWHVxOVkwQ3lTUFJQX2xCWnZ6V1lJNlJlMkphOF8yRUtGN3EtT3dNMGlheGMwaXRfZlh4S2x3M2N3OVFWNThFNHdlZ08wUzV1MTdCdzdOU0pyWHRnS1NnRmI0WXlISV95aldTQUhqN0g0VGpyS25zMHhOcVI5VDNhOU9oR0ViRkVuVFZoci03SWhDNHhhaUF4aE0xTDFtVklOSGt1dkkzVzQwcGxBLV9hLUdUdXZhME5aS3RoZTZ2cjlGNHdSLXNaMTF3TjJmeFc4SFBBUm9ndE5mT0RWeEc0RDNIbENTS2ZzdHhvRDA2ZERKSzU3QV9pX0VhQjZwSGZ5dXlqY2NhRkUwaWYwMVZXazVHYXkza25qRHJHa2N6dG13eHdmOWJoY2VZenpDUFdPM2dMc2lwS3gyelRFV2xhbFlqbGN3QThZWnFCdXdULUJyUDUzVEV5YlhCZ0FURG5kblBJY2hMLS1KM2x2UXhhOUhCZjNRQUNGLU9vMmxHbV9NemVBcmUzUC1OMFQwV0tOb2pzcWE2bFJOR2xVUGtHMXdBMGJPMmlKQk9LcmJNbVNGd1BzU1M0TG9lREhZWGV0bmJLVFlhRUY2TTNtNnR3dEt0N3lkYjhIUllGN2NfZjljUEtxNFgyUFJEc2N3enR3bE1BMzhNQTJwQlZ1aXFJd0xqSlBNeGJ5aS1fVHhYc2VoUzhUWXFkdi04WTh1eTZnYU1CNjZwQ2huVU5wTmNaWlh5bWVKclZ4cXNNdnZsMkR4eTBDNElvTDZQanc1RVBzZlFhZGJOcE9XZkNmM1l1SE45NDJGZzc4b25iT3BNNVlqd0F5OUFwN3d1V2k5WnhoV2N3SVNlcmU3TnduckZkUkJ6VXYzbEFtZ0hkNXpaSlc1NW9Wbk5KZG10Vmc2Y096cTVFVjRFX2ZYeGtCdmNjZm5aR0pfSlpXZ0lfNDROcDQzNTlFRnNOUUtlX1NydHdyYWRxWTBlejFsY3NPMFdmUHo2cjAzblZwR3RaQlhvTEpnVHAwN2dwR1pOcFBuaVFyeGwzTnpmdDc4SlVSSFFJZ3ZrZE1ZSnQzTWY2bWEzVUdrRndmYkFCOEU3WG15V0Q3VDVvY0JEOHlqMFpaNXNtTVVFaVpsVE9VbWNDSVhubE0xX09PMjFORmdsRWVSUjQtNHVzUDhma3dxdkJqQVNHN1MxTTNKaTJuU2c1Y19icWUtTDB3NWIxT05QR1NjcWYwWVhRVFJmYlpoazVpMHFTak94WlFDcVZHNVR3UmFjTFg2WHRRY2llU0ZXYjhiV0ZDcWY3VjhiZmd2SWpMQnlha3VzZkExdVl5ck1YeEh1alVWRFRTMkl0cEpiQXQ2TWNaeXhGNU9GeE1HekoyNmluOUJXblA2Vnd4YWNVVnQ0YUFyeGRKeHFrSW9KOHNNWVJ6eGtoQ21oNFEtcExudXZxUXNVUnRteng3Y2R6WWpkb2tRLXZ2T1ZIMms4WXBpOUY3OXZITFRDZmMyQ3RTRzVyNlRRMDdNSGg2OG95ajFJVkdoa2JBMXBwMmQxdERPcVBaUmNLREJMWTJPUUZoV09KMWpGUTd1ckdjaW1wX2JyQmpuaGJKaFRobHBMZUxrQWhYYkNYb3RnSE84S3cxa0pQdHh4TTdpS0pzUS1odnl5aHJFanhkaGFpUFRINjhxaTJGZmxLSmRMc2tFWHpCRnNUZkRmWXVGd1RsdHljaEN4eG4wVjUtSTd6a0JnY2JQdGl3TldfNnNySzhGUVFQeWFnX2V4NVJ4VlRLSmtOZXEtaFdKYlYteTRLWlVDdEIwSjl4UE1fVmtUMEQ4V3MxM05neUxrZEgzQ1daeUxmN2hULTdDeDlXd2ttdVFXZC1QWUdxZHBYRkZheFg4MFdoUHVCU0U4dHFmSHhXNGR3SkdmcGg5ejVNSU9VTkVxdmRPZmVJTV9WZkhjQnVzTVpzR3RDM180T2xIdXpvR3lHam5TTXQ1NU5tQ19NdGlrckxNeDlLY3RWWkQtUzVuQkROZDZ2OVJIcFFlQWlPWU9tdlZWcng5b3Q2ZGk5a1BoXzAxdGFEam1ZamFUbktFb3pheHphSWM3cTlreWI5R21LYlMtUlZobjQ5LTVITlNxOXNMdnR4YVhUc3lPTk56UTlNSHp5aGNrcFdVc0wweHphdzluMFR2VEhzNXdicEJzVzN3MVpkVEVhRFJaNXZXZHNRMFFrUklGWXR5X1RyaGlRMFlZbVFtR05Eb1ZOME01N0pkQUlmb1hRS09KRWg1R19vNC1sTUstYUM0X3BiOWEyRHlsSGtaU0FFU2pITlNubnBwbUdfMFViMlFCUktQR2N3bHBCT2xaRWJKYzRfbHVTWVhxWFljODduSDFPbWhLOWlHWEtWLTAxQlloczM4aTAyYjJlTHo3NS1lcnhsVFFEUDROQkt6LUZ4VnFnTWY2eDZBb3VaUnY1cVJpb1k3ZWxjeGRhVHJiMGY3cThlenRLd053LWsydGVBVzJXYmlNVHhFX091U2VPTVcwMXUzbS1EU1owSDJoVHc0OG9UUS1pWDJtNVlkem5mdlVkTVVkUF9pS1Y4NmR5TG1aZURTeGJyUUdSRGdyZHRyV3BvQlJTMDVJV1l5UjBhcGMwT0s0YTZVMENDWWdoZndfd1JINUctRWZKUERhS2tfZng2UjRZbkZjd0diTUdyVEQxeXF0UGQxeURwVWF1dUVUZGp4VEhrWFNfMnZ1ejZXZjlQN0JlcWdKdXJmZ012RHZMcDQ1QjQteDNyVEktZUxzcnNaRGdHOVY0TS1YUUhZZ3NabDlwVU5IQzFyVHg3MjNSQmlZaVZOcXlXVTBlTER4bEVnT1ZMMzNzLjgwY3p1OHVFWmY5aHdMNmhlNWdHOEE"}' headers: Accept: - application/json @@ -165,12 +218,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault2e80e6d.vault.azure.net/keys/restore?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/ada51ab3a12044228eb5220085ed7fd1","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rG7TKs70mQqtxO-Xbt3k3pXWYYotG3GRgJlc108EDEuZWjY-Itzbda_DXmOFbXOTdBl52m9DCHQOZx8RK8p98muUKF_Dpmh0S1QsmfV65_5ZtY-lOOoyCbQOhjh_AZxcJ0BQ8oMBUyBFOt1ljP33ABGZOtVYLZ29ghk1FNtTN5UK20nnCtNFbmDlmtcxjm9d44pwgtQlv8Qdwqv2yS-24qnU-VXpkW_1Uj2zIXBbgPXMoCIZxBDq5NPKiAczHKEBqu-As7EqTeuSkWtB52zzDaqCWD6yICbxiRDiPGf0g2hCaftFU0AtOOprQsnFTwqw9BYwitlehgY6ap3yoCkwpw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault2e80e6d.vault.azure.net/keys/keybak2e80e6d/e37af5aca9214f0da06fdcdf9b3673f0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3whdGIk7Kq6uXQsBCR_9LkLgcdBzTr_YUP7862Gqs9vc2d_dJcYw7yG48R37hir2x76m1FOETbmgJw-tKcxzNvGIDxOvM4xCPAvJhx3rLN96-sdUStIcVmbiQ3rLsiaRdjvwHfS4ZGamgHGFDBuupS6Qc-hFvERUOVNUDZMEOZ7HEON7HCD2_o7QUFef7JLIz9JYBBu1b2JU0hgfINkID7NGOhzIBvBRy0phEycM45Fjy8m3w2vn5bKKWEfsS1QmW3QUjX6pCY8UE0PgB0QJ5hJfYiZUPi7vot6I4tJlNmx29ngzJ7qMKbxxnTVxHjQjB1GLQtVBCp5pAaAM_GGv5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -179,7 +232,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -193,11 +246,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_crud_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_crud_operations.yaml index ebfe15b3423f..f6dc0479ab50 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_crud_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_crud_operations.yaml @@ -1,7 +1,6 @@ interactions: - request: - body: '{"kty": "EC-HSM", "attributes": {"enabled": true}, "tags": {"purpose": - "unit test", "test name": "CreateECKeyTest"}}' + body: null headers: Accept: - application/json @@ -10,26 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '116' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault51c4108d.vault.azure.net/keys/crud-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-ec-key/8dd7f342ba174123a548a581b5d8b4ed","kty":"EC-HSM","key_ops":["sign","verify"],"crv":"P-256","x":"2nq1jdczQFJTmh9zyZXjEIXNPu9_Qmzvo6zHYuxmoSE","y":"UTy3LwgY6eeHvapIPy9tUxOoKyVSh0dHBjun1ZmSFIk"},"attributes":{"enabled":true,"created":1560986262,"updated":1560986262,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name":"CreateECKeyTest"}}' + string: !!python/unicode headers: cache-control: - no-cache content-length: - - '435' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Wed, 19 Jun 2019 23:17:42 GMT + - Tue, 09 Jul 2019 20:21:49 GMT expires: - '-1' pragma: @@ -38,23 +34,27 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: - body: '{"kty": "EC", "crv": "P-256"}' + body: !!python/unicode '{"attributes": {"enabled": true}, "kty": "EC-HSM", "tags": + {"test name": "CreateECKeyTest", "purpose": "unit test"}}' headers: Accept: - application/json @@ -63,25 +63,26 @@ interactions: Connection: - keep-alive Content-Length: - - '29' + - '116' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault51c4108d.vault.azure.net/keys/crud-P-256-ec-key/create?api-version=7.0 + uri: https://vault51c4108d.vault.azure.net/keys/crud-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-P-256-ec-key/fa16b4e757734590855f7c58021bfbc7","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"fgiHaY9TvrS71U9F9dCvL07W6QlrLGRsqUTJhy0F83U","y":"4j0grmh2l_lP2yMpQqDCkuNmNZH2Q8a_yYiM4z4cIDw"},"attributes":{"enabled":true,"created":1560986263,"updated":1560986263,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-ec-key/412705b9e9544cfbaae9aa6def9473f7","kty":"EC-HSM","key_ops":["sign","verify"],"crv":"P-256","x":"SvoVvhsE2xNn9yoCyiURjuax5xg2C6hdprD96kbSmmY","y":"PzWgi7lgIuSK9y0_SwI5xJsvzh32Jtwf9wfGF36Mfko"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"test + name":"CreateECKeyTest","purpose":"unit test"}}' headers: cache-control: - no-cache content-length: - - '376' + - '435' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:42 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -95,25 +96,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"key": {"kty": "RSA", "key_ops": ["encrypt", "decrypt", "sign", "verify", - "wrapKey", "unwrapKey"], "n": "AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU", - "e": "AQAB", "d": "Ynx9JGaBSP4iUsf6ZJ6opantRNdcdmzaQrKbZg6ZQE8Ohi1FYabJWvaoPSE-CiJEsDzShXZHMhUHN4X7Bn8BXaGQhK3p9HXgiwQKmix7oAJTu4ElUIyd8UC3UWHSZr40el4PaQD-HYu_eMzCXus34MnRiNbh_BUWm6T-Eidhk9d3kNIyaSi9YNDQHW6tjWrEhhq63O7JU1j9ZonFChZxpKk20jdkQKQURVAdpOdL-5j4I70ZxFuU6wHZj8DS8oRQfwGOvZKbgYDb5jgf3UNL_7eACqq92XPVX56vm7iKbqeyjCqAIx5y3hrSRIJtZlWCwjYnYQGd4unxDLi8wmJWSQ", - "dp": "AMmhWb5yZcu6vJr8xJZ-t0_likxJRUMZAtEULaWZt2DgODj4y9JrZDJP6mvckzhQP0WXk2NuWbU2HR5pUeCN2wieG1B76VKoH76vfnaJDqT1NuJVBcP2SLHog3ffwZtMME5zjfygchG3kihqOSpwTQ9ETAqAJTkRC38fEhwAz_Cp", - "dq": "AKC9TAo9n2RDaggjdLXK8kiLrBVoaWFTpqXkzYXRhtsx4vWPAkxhfSnze05rVMl6HiXv7FnE0f0wYawzUJzoyuXBH0zS6D9BqCZPeF543AmWB27iPf38Q9Z8Rjr6oBgMSnGDV_mm8nDVQkeaDyE4cOZh-5UKvKShTKKQVwunmDNH", - "qi": "AJ_nrkLpK8BPzVeARkvSHQyKwMWZ-a8CD95qsKfn0dOZAvXY-2xhQYTEwbED-0bpTNEKbIpA-ZkaHygmnzJkNbbFAnb9pkkzU8ZQqDP3JNgMfVIroWx58Oth9nJza2j7i-MkPRCUPEq3Ao0J52z7WJIiLji8TTVYW_NaiM1oxzsH", - "p": "ANHerI1o3dLB_VLVmZZVss8VZSYN5SaeQ_0qhfOSgOFwj__waCFmy2EG7l6l6f_Z-Y0L7Mn_LNov68lyWSFa2EuQUeVj4UoFHc5Di8ZUGiSsTwFM-XMtNuv8HmGgDYLL5BIJD3eTz71LdgW-Ez38OZH34b7VeG8zfeUDb8Hi30zz", - "q": "AMPcZrZBqbc82DO8Q5zTT8ZXRGWrW36KktMllaIk1W2RHnRiQiW0jBWmcCgqUcQNHa1LwumjyNqwx28QBS37BTvG7ULGUoio6LrOeoiBGEMj-U19sX6m37plEhj5Mak7j3OPPY_T9rohjTW5aGGg9YSwq4jdz0RrmBX00ofYOjI3"}}' + body: !!python/unicode '{"crv": "P-256", "kty": "EC"}' headers: Accept: - application/json @@ -122,25 +116,25 @@ interactions: Connection: - keep-alive Content-Length: - - '1724' + - '29' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: PUT - uri: https://vault51c4108d.vault.azure.net/keys/import-test-key?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault51c4108d.vault.azure.net/keys/crud-P-256-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/import-test-key/686eaf34323947d0b2ea3dcb5f4ade6b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU","e":"AQAB"},"attributes":{"enabled":true,"created":1560986263,"updated":1560986263,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-P-256-ec-key/6157679407e449da80302952da0add25","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"ecO8-9TBNulxLBsPbe_GUNplpcUws97g5hg5KeVcI9g","y":"DOfRfSjzdnBDbTZh0tvSRcd05WKd2qWxNGjxgZlKEuo"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '664' + - '376' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:42 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -154,20 +148,25 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA", "key_size": 2048, "key_ops": ["encrypt", "decrypt", "sign", - "verify", "wrapKey", "unwrapKey"], "tags": {"purpose": "unit test", "test name - ": "CreateRSAKeyTest"}}' + body: !!python/unicode '{"key": {"q": "AMPcZrZBqbc82DO8Q5zTT8ZXRGWrW36KktMllaIk1W2RHnRiQiW0jBWmcCgqUcQNHa1LwumjyNqwx28QBS37BTvG7ULGUoio6LrOeoiBGEMj-U19sX6m37plEhj5Mak7j3OPPY_T9rohjTW5aGGg9YSwq4jdz0RrmBX00ofYOjI3", + "p": "ANHerI1o3dLB_VLVmZZVss8VZSYN5SaeQ_0qhfOSgOFwj__waCFmy2EG7l6l6f_Z-Y0L7Mn_LNov68lyWSFa2EuQUeVj4UoFHc5Di8ZUGiSsTwFM-XMtNuv8HmGgDYLL5BIJD3eTz71LdgW-Ez38OZH34b7VeG8zfeUDb8Hi30zz", + "key_ops": ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"], + "e": "AQAB", "kty": "RSA", "d": "Ynx9JGaBSP4iUsf6ZJ6opantRNdcdmzaQrKbZg6ZQE8Ohi1FYabJWvaoPSE-CiJEsDzShXZHMhUHN4X7Bn8BXaGQhK3p9HXgiwQKmix7oAJTu4ElUIyd8UC3UWHSZr40el4PaQD-HYu_eMzCXus34MnRiNbh_BUWm6T-Eidhk9d3kNIyaSi9YNDQHW6tjWrEhhq63O7JU1j9ZonFChZxpKk20jdkQKQURVAdpOdL-5j4I70ZxFuU6wHZj8DS8oRQfwGOvZKbgYDb5jgf3UNL_7eACqq92XPVX56vm7iKbqeyjCqAIx5y3hrSRIJtZlWCwjYnYQGd4unxDLi8wmJWSQ", + "qi": "AJ_nrkLpK8BPzVeARkvSHQyKwMWZ-a8CD95qsKfn0dOZAvXY-2xhQYTEwbED-0bpTNEKbIpA-ZkaHygmnzJkNbbFAnb9pkkzU8ZQqDP3JNgMfVIroWx58Oth9nJza2j7i-MkPRCUPEq3Ao0J52z7WJIiLji8TTVYW_NaiM1oxzsH", + "dq": "AKC9TAo9n2RDaggjdLXK8kiLrBVoaWFTpqXkzYXRhtsx4vWPAkxhfSnze05rVMl6HiXv7FnE0f0wYawzUJzoyuXBH0zS6D9BqCZPeF543AmWB27iPf38Q9Z8Rjr6oBgMSnGDV_mm8nDVQkeaDyE4cOZh-5UKvKShTKKQVwunmDNH", + "dp": "AMmhWb5yZcu6vJr8xJZ-t0_likxJRUMZAtEULaWZt2DgODj4y9JrZDJP6mvckzhQP0WXk2NuWbU2HR5pUeCN2wieG1B76VKoH76vfnaJDqT1NuJVBcP2SLHog3ffwZtMME5zjfygchG3kihqOSpwTQ9ETAqAJTkRC38fEhwAz_Cp", + "n": "AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU"}}' headers: Accept: - application/json @@ -176,26 +175,25 @@ interactions: Connection: - keep-alive Content-Length: - - '177' + - '1724' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: POST - uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/create?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: PUT + uri: https://vault51c4108d.vault.azure.net/keys/import-test-key?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560986263,"updated":1560986263,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name ":"CreateRSAKeyTest"}}' + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/import-test-key/4d06554748084cdfb282d73818ed909f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '723' + - '664' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:43 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -209,18 +207,20 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: !!python/unicode '{"key_ops": ["encrypt", "decrypt", "sign", "verify", "wrapKey", + "unwrapKey"], "kty": "RSA", "key_size": 2048, "tags": {"purpose": "unit test", + "test name ": "CreateRSAKeyTest"}}' headers: Accept: - application/json @@ -228,13 +228,17 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '177' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560986263,"updated":1560986263,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit test","test name ":"CreateRSAKeyTest"}}' headers: cache-control: @@ -244,7 +248,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:43 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -258,11 +262,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -278,12 +282,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 + uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560986263,"updated":1560986263,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit test","test name ":"CreateRSAKeyTest"}}' headers: cache-control: @@ -293,7 +297,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:43 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -307,19 +311,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"key_ops": ["decrypt", "encrypt"], "attributes": {"exp": 2524723200}, - "tags": {"foo": "updated tag"}}' + body: null headers: Accept: - application/json @@ -327,27 +330,23 @@ interactions: - gzip, deflate Connection: - keep-alive - Content-Length: - - '102' - Content-Type: - - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: PATCH + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560986263,"updated":1560986264,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated - tag"}}' + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit + test","test name ":"CreateRSAKeyTest"}}' headers: cache-control: - no-cache content-length: - - '668' + - '723' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:44 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -361,18 +360,19 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: !!python/unicode '{"attributes": {"exp": 2524723200}, "key_ops": ["decrypt", + "encrypt"], "tags": {"foo": "updated tag"}}' headers: Accept: - application/json @@ -381,24 +381,26 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '102' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: PATCH + uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560986264,"scheduledPurgeDate":1568762264,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560986263,"updated":1560986264,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: !!python/unicode '{"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703711,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: - no-cache content-length: - - '803' + - '668' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:44 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -412,11 +414,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -431,22 +433,25 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault51c4108d.vault.azure.net/keys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703711,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + tag"}}' headers: cache-control: - no-cache content-length: - - '80' + - '803' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:44 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -460,16 +465,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -480,12 +485,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: crud-rsa-key"}}' headers: cache-control: - no-cache @@ -494,7 +500,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:47 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -508,11 +514,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -528,12 +534,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: crud-rsa-key"}}' headers: cache-control: - no-cache @@ -542,7 +549,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:50 GMT + - Tue, 09 Jul 2019 20:21:55 GMT expires: - '-1' pragma: @@ -556,11 +563,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -576,12 +583,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: crud-rsa-key"}}' headers: cache-control: - no-cache @@ -590,7 +598,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:53 GMT + - Tue, 09 Jul 2019 20:21:58 GMT expires: - '-1' pragma: @@ -604,11 +612,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -624,12 +632,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: crud-rsa-key"}}' headers: cache-control: - no-cache @@ -638,7 +647,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:17:57 GMT + - Tue, 09 Jul 2019 20:22:01 GMT expires: - '-1' pragma: @@ -652,11 +661,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -672,12 +681,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: crud-rsa-key"}}' headers: cache-control: - no-cache @@ -686,7 +696,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:18:00 GMT + - Tue, 09 Jul 2019 20:22:04 GMT expires: - '-1' pragma: @@ -700,11 +710,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -720,12 +730,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560986264,"scheduledPurgeDate":1568762264,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560986263,"updated":1560986264,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: !!python/unicode '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703711,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -735,7 +745,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:18:03 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -749,11 +759,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -769,12 +779,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560986264,"scheduledPurgeDate":1568762264,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/cc07dfda5ef54f1aa87a07a078f41aeb","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"wx2K2L_UAobrj9qUR8F_TEdIan84uE0w--t4v8y9huHGww75UHwOBXalKhhgQxRbKvXzdpTYNhc3pOSb6qS0s5HI0rbDt_QigYC3i5JT-uTWAKQb_CcsoeOWoVkZsI3JyyCJVKpR81TzEAYRFRXJSzHGuXsDlN96Wa697rIqKENS1BymTP8Z4gbks4u4O5PmF6Trv741c3ipkOWEBTKa5dp6avx1UK1_f_w-UTPewqRV_zLA6B1L35aXqay1VxzwWuRrKx8TT9yAmqCwJRpILWAKYM-OSsELSww9tTX__2D49ozCQk2ZUPhAKfqzwzNYbjUjgSYAigCbVKwmInfUFw","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560986263,"updated":1560986264,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: !!python/unicode '{"recoveryId":"https://vault51c4108d.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault51c4108d.vault.azure.net/keys/crud-rsa-key/f339f327d0d04014990f7db98309501e","kty":"RSA","key_ops":["decrypt","encrypt"],"n":"zX-8UthpjBmvYCnbmbZTeT2_fh6jWrTC_ateiuvSNYaxPwIUT0yf6wYeXipjqwIESPQaRvQN43hJT0ugYTj1f-K-xg9_Ckgi9zx2F--L9XzHSsCQzyq7A63IUvFyRIiigz4eE5bNHBEsUAg6Ft1OR8fdaJL9Jy5ej2vlVwbJK9r8TiKVlYeewY_RC2406IT2rqRHBG1rTRf2v38gKbD-QFDewSoJJPH5ESHpRoh-Q6LriMfuMmg9ZtqX0QqDT4VIXoHkhrZQkI9mCKiC6L_Gn0NiTzMMeMz3hVBJ-iDesACXLddgAao80upVp8hFpUjiTPs3VPsbk6LklRTNwsXBGQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703711,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -784,7 +794,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:18:03 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -798,11 +808,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_list.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_list.yaml index 9859aaa70b44..d4f55c693044 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_list.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_list.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaultb3510bf8.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:21:50 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: !!python/unicode '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key0/87e785ad4bc24ad3b51ee9e3f0efc656","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1-j0wPhxXBMMCoP0-yxVCfR2hRRpD-ANn9eghyROMaX_t0VAusi4rg7u_rrUp1ZkWa4G90Rjd8ZynTR0ovCWnIMPygENiqEEe0LqOsSbAFoqO-95stodl2zR3bXsMnellfA1y7_jWQ0QB0g9iPRz4U76zjJ22yLblZmO_Hb1Ae0Tt-KY9S6sUOm5VvQzDwLMonx_uK1rhtppxmBghNboDptlzozslzcyMx-Yuag8gZ4gU_xxAB1VYaWbSBGG9X5yNI2DrlE_z4Tt0hXJtFxLasoNIGyuNqt7Ty85WPkSB8N8DZ0MtjWLkq79vQlRwqj_IOy8q_63FaJImF6Vc9Xu6Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key0/b2fcd7bbc78441d48978bd5b8bd6412a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pYfuVdAcTMy2LdSGNuXii-DBytW3KTp9JzpHEBarTu_Tf2p7bvzM6SEL85OZhWMHt9T0FSzfA6NOhjSLk7_sGa12J_Gkiosm322x-9ko5PEq-GO5-FzjGw0vtW_q-SrFZtCDSD7GaPNJihqOZvAXO4wvxTQl7y4KSY4lPErn4YyRRGTGw16EYiXeQGRSvgjjtKLxcMiIH-41RlH4LjcXl3XyenGWtK_gQBnptYi96BGmXlPgQGUHH7eAciHqYfxLi58cEcfN56EU28YavtuqMBzu3GqkJvinTkLUYtbczs9c6Jo1PzHoYiyhTgOfz2kcTKCYOXCtnatlcX3go9X-UQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:12 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key1/45b4d3cb1169438ca2dfffe9bcface6e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"l7nbpGdAqqlmMFLKSMDiXIRBmWZ8P_g-EuPoTJGjsE6SnrXAKvNOlGrZ_7GRNQ5vfGDTSk7w_CczZuyQ7unEGdyaZFOIYX17-rf3pLJhjk4EMDvgn-ZhxscTI7BeLQfNm2zqUY9lXrPhN24sbkQOo7Ju3KBuTV9VTnHZcj6kE6h6znxnZDkLDb881dd1Wm8D-RgdzSYPgYVxFrI52Daol-hHHvv62VIWUxwRyf7v69LBliNBev9Bu9RpZj6ELMUs9eWJNMEmbciIcY66IQKAkW8qrW7KCIxzkpEORScq-F2W5kHPds54IbmnsAf_tf5kptr2986nV3pRklaWXbdArQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key1/f8c0561733dc4ef8b045541b76ea8bde","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vdPvvqrUNwKroxP8A_i2NHw-9fd4WxQSSl2cSDU31C1m2gG5OsfX7Il9gbgydjUJNiNG74128NiKlJrccf52G-morvQUOu3Sgu-Dm50rYnHlRV1QSyLz5YD-hExZpwS0OnU8uEBKde0FUUgNgyyeRWs-t4IpMPLuEtsvx0vgciptEletBQ73IQm6CZg8Q4wqYTxF9YYVycj1kCb7p4jqSLGx78BHEoq__XMbz9xYV17lvWER5c8IIWFnZKxsxFTJBOt6Vgq-Iua2e0Aoe1nCkYv2-oE2O_zY-hXWouIzFjSx6qyuvtVKLahNyht2LiTMofBCa4CKSFvraMJBYBR1Kw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:12 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -93,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key2/e7fc0bc1f2da432c8cb4389e7e2511ba","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0qhRwpvqkTBd37eIzNKMScRKLC3z1562LDrHCHdW0QJp7w3tcCTGADYIrffYJ10VzSBgEuryzeqa9E40YFKdvJkjd-HxGJkt1To36OZegwyza1LmbLxmCFGHDyVT2bbX1JprVdHHdtklx3E-2shouYrHIhrtpgaiS9LA4swIko-HPwIaclcUgjOwj3iavH6s0tKhF9QxQMEZy9KTt4b1aEHvtMFqvlw6aCQkxFHvN77-eartFdtjxb5AyQ3nAe_598mrYQshhdg9zRf6P5SKnDwU1Q2PcUTvgAjaXumieqkkGd3m76F_yTR2JSVmRqnDV_tACti_JpthnKTTmOuYyw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key2/853336c127b540798ab57885e87ef156","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"w06JdzoALMiTCJ0SA_E6Z-rRnFpa02vLkh_TKi9uH6_uNmJ8IOZlArvNr6MhqtCWBGKcZ1dmCpN7DaMvr04GciXWRalXZ7-eX_utok3dVTfuFxxq18UDCYcHYSWa9U8FH0YrnqCxRJefHGTtvijJL2O6CcNjNpd6uSSn7EIo3YTSSE1-wKFgYHBQt5Ee2BadXz7mBtUOlb1FgVfON4IminghQenE1J_ciOVTEOnm_TaMyRupOWfHLg62oVWbNSWmTcVK6RdX35ibsjwPOFl5jSTM07aoysaH7Z-Uxy4QOnxw8LmLG_2fTpkDpvTiAayYirmuODm1dplgjsRU_l-K3w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:13 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key3/ade541dc904c48e2b95e81839b8938af","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"oq0iUuCyQ3jH_p8ki2tJOsI9XOomtbHSNesnFlqmr98DwuyWuiu_M7XAqPGJEBRm1MeWBAAQFQe0IXWrE2eI6lXDnGRMcu7xzEtgvJJwvCsgbJYShgDvYjpxZlcmF0rIHOK6bNX2gIc0Ba8yhClK0E8gbe1gdiwyC-NEnyEuAZqoiJdlvgoHyaZ0xDU1Gk3THc1A32dbk96Jdps1MTa38AiXh6GVWimv-E69RTsU6tSUXEtXLIdUko66e2qa70ljE2XA3Z6wghiCkG2M3Xm4BtCQl_4fk0OyKYbTrggspBBsyApGHycB9oPOPcB3AzXCqRv9MfLTtc4YDv7lXLI9Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219254,"updated":1560219254,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key3/645049f018ba4cf4866d769a31ee4367","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ktbxTTJzDGQzWlhPmLsLHa7u0ijHPIOvVvm-ovcHVKumsjlCgILsOff67p9tzk84335jLsM9HxWS41IlRQQ8FCfzEvfGOpliYMV0Bp_dpqUmxQZSpQivUy6yMFf-22CS1TtZvwpAJV7SRD8POAdzHJ1KnoAbgw0aW8ek-vf-DITio5hGKE6rtV4yLOAqZ01x0KvXbxx9_CNxbsw4L_kmUE0Dkg-zHj-8bQd_mUoutIgDDB5yBbi8p6MkzFJcCkcUl8x0g5tu86tmwnSw_W5spIs5NSzO1g0KdIHhYVVPMKQgwXG-S1XW2iwWKBcehba7_yzBQUN5ZBHcvTZQAF_3kw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:13 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -197,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key4/fc51f17d8bef4ad5b84e475923392fb0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"i8kCFSk1LOBge_qvxT8m7K8xnRXRlhXHCVt-gX6fymhUzbC197iDr6V_VUJZWK80A8QqWpvCmMP83Uj4qkM53Iz1BCCdQRiJ5_JlrpQ8IfY3sCEDngnaFxHSXKuhE-f-huVFxK9Q5U4waWeHDeNmBLZw5OqXau9mL9xW9JXGmswvUWXBsYl_8NgxcIrfyeE19jA0abaRov_GpMuj4l8t2ASZVvyGKXVVxpH1WpMCzxNkKC92TL6mqNU442IG3MBlZdSBOGKeM18uCZygC295UaidtxMqvAt4LccHTMle4EhUBA2UT14_9zcEv19mEpO6S40yVqwgZzEPiZziSujzhw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219254,"updated":1560219254,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key4/c11ec1c78a294bd4be7cdac6779bb25d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ytIe9XMa_9pi0xLvbLZj9EUwj5Cu6g7vSIMMpL6DYnXHNofbm-B2H5EOQQf8aO-nwRISkd6lpQJeLgQtuxU9RIjU1MiEWXqgJeHc41zJEkefrX22EaH2a-nL021-TAYxi4agxFbQSXuTYcMf7VLP2YrhsIXQmBNOV73dnYrFYqd8Pna-SdBwZTyqYQSU8RIpe2EUUqJOVq8chcpXQ4IfFsl_6CdDEqcN7Q9qBhencuwZeUH_mCyiBQ6FYtTaQZjFAoueRUH6hyYqqERtP0ApQKJNjaZlU_7efouH0keOjyZJMbMSc-QyLm-8Y0j4wcsZ2W5FR_EVYQlvU6myO9T2ew","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:13 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key5/5f61f28c70b64afaaf8ce4aba3273acb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"su92hzrMEKexBHd48m32Hm4f8KJNAjkYuxp47JJbGRDPNK4DCFqSOjh1uf3-FGVhEnTmUEcj7tfn8nCwJe10w8gPe5bX1FZhhOkSOUu-RSyvoRF5dwRhoRVEsmqS7h_IiNB62sTczB1sZlEw8ZwYl7lMH42lKEnCxD7rr0mem52Wdjv79_2qvSzvmHts0mTrVZGewdl9biRkyXWL7n8SEQ9DhROzoDaOWp2RhOIWB6by7rGUx_B6hJGOiqlRrb1_KNBTWqQXkTK3AdXbtn9yD99zCWD257r9xgmV6fPrbcvRk85cpxv_VLIdGaa_xoOJglXt_kgNuViaBNetqyK4pQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219255,"updated":1560219255,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key5/bf7be1e41e644396b9fee1850b202ad4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tLEi5u2VI5ZRmgDY3riKmxedvTYKYRbcoZxVFL8SUAdiNh-FdL9ScjDevs6jcTGG23AXZRAY4DWvQ2pEvCpz3nmfoDsEVH239HhBJu3lD171-YtzLV5gaqAeIwNTIfcbtkc2KnzXBgL1eTBz_XeRqydwti8A5bQyPvC9bUYmp1fk5hxDnPIMaJEqghrS_HzbUWd-anh7u5F9W5MZuxJ_vjXswBDkTiTTvwJCv7-zb1EVyIINVCSFV5l-kwVdX_v_rYrV8EGW1FEBipg9fn82_0TcPNZy-ixULXTxY2N5UVaFOSqNz36pn6jnBIxuamzs-TlYUGfTq8qgXPTVoo3eAw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:14 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -301,11 +354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb3510bf8.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key6/e45bd6acd1614fa5add653a5bbf2c93c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"t9QKWFr4meD0rd-uTLrdlHK3SldutPS73vgmAuDKNcKS3IiOvn1knsg8Ibnq0igNhWk-gOJuIP5R75FND5aXt34UhBY8ZrMLUNJWbgMBnOEQmLdkPIOZI7n1bAsm2kTAgrJVP9A4ZtITHE3mSGwlGMAH8I2D73idt1sIpqyNPIsNMyjjGZ99zggC0Y0jw5HaPdPXLowjmuKlsm0iBWGJT1o0nBhW93wP5QrTbI2pLdRkjhqvY0LQWEreI3HLzm1yXXCS42BWhBQfHyr7KAd4ztuqze4eGdJuW9gPN47VHZHzvMw-5OIgaTF1wz0kJ_0YHaiKeGyd2thVTLisaJBiLQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219255,"updated":1560219255,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key6/2bdd9d0672cb44dba8040ccdff45a607","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vdv21F0auCmBSFn0Uc4SI1-249xz3XZpFOW9PeAuu-c-nTgPxHH2z-cA5f8nGxMQ7cbMVYq8hfo5mGWQ9hKnkTt5ddDzVIzRNknqrOSTmsZByXjiwF4s4oN-8tbW2U4YyyyjMidi8gbo_arGgyopYrbf3sZvy3-9zQzgLNqjyMA8S-7SupiLVzq2pmESrCVPASf_mTR4EaHaELfrm-gS7JIPLZ3Xr-Tecyyyc5WX8hAs2_3QfwHLWoWaBoHywqCqpJTezTIJ0kO0aSwGo9mSyIdR8kW2xtEbpyix4Ay3efWS8re5TwC4HlqwOveUdXbU3a55YraR8zOEjygAZ7roZw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703713,"updated":1562703713,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:14 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -373,12 +426,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaultb3510bf8.vault.azure.net/keys?api-version=7.0&maxresults=7 response: body: - string: !!python/unicode '{"value":[{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560219253,"updated":1560219253,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560219254,"updated":1560219254,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560219254,"updated":1560219254,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560219255,"updated":1560219255,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560219255,"updated":1560219255,"recoveryLevel":"Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb3510bf8.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703713,"updated":1562703713,"recoveryLevel":"Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -387,7 +440,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:14 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -401,11 +454,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_wrap_and_unwrap.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_wrap_and_unwrap.yaml index 9f6b2e665701..c5b89f14b2ac 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_wrap_and_unwrap.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_key_wrap_and_unwrap.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:21:49 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: !!python/unicode '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1s7imAHLUFtA5KixsltFMl7L7hHuLB9tBC0NJ8Jy2iz2poU88aCXn6827HSZBfBJIcLFre2aWV8-n31p3uxSA1_nECY1KxGemZFXTAhKfKVdJKVVTQyFV25ea3euJpPrcwnCCYxbT4zr2d_VNvVszP9nx6wQhLUPNNNrzdggH2mW4gLEQJ4Wa0HaOsJInUr4-g-h5TDt0ziZ6PPvUB3ouZCUL8YzLPBZP4UHmwlINxCmOS09uf9ZkhJMQrKi6B7910Ws4NpOqd4ia0UclSaD04yS9_OVpP_SpMp-TClbMn0sHfJf_32NuW9sK9fZmj6WySNZardTsp85i_f6mcQtow","e":"AQAB"},"attributes":{"enabled":true,"created":1560219178,"updated":1560219178,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tVBTAmld1HXGkTB2VoEnLrnc4HCokRsugScewlnc-k3DWn81AlaGcnnZw2P6VAHi91thNEe2qsvtPOjGu_fG6E0RfGv0ldXdZHP0kIBJVwk8MiotDvI0t7xrZOFbMJ2LMzqDItQgfWEVknjkSPkUrhhGOMoXJgdawYUHKXiw04xkSM-O96X8p8FDFBSyqgUvNzF8VrRwuAOkIKrTLfHbKdqh6pQslUXj5MTcZgRlflHk7dzpsR9KKAo1mCFptfOlBns0-EFXWF4j8Zd8tEf3WJSAOcPS21oU27G-YQtAmUu9z04j9k7OhBpSQr3_2LIneu14GSso8oituIbIuD6zUw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:58 GMT + - Tue, 09 Jul 2019 20:21:49 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/wrapkey?api-version=7.0 response: body: - string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11","value":"X0G24oDAbZuZdMSUHu8Blr0xdV0ba1qqmgzjRmpKPz6tckCacpET0tcOdM0f8McEj-D7B_jou6yx9P81RWsaLHqkLa8K29sZgSKIh3rcoHDmf7snXgWFoRVX5lfbCJsii2hMrGYKeaLNS2tNFaqbhVlZ34SNUL3YOsQ4b-dLzJ8QQblG-5EQlhPnjcCAHXloC_hM8QMw8EmmMZFALPTuro1MLlnPkSnVxjnyR__vU4SqBRN1fDKHSw-nY1vsWEzQCx38TDkSibV52Qhic4IWXH9wWzKAFRq0NYgra5VD6N_beOzNiUiSM9g_3ltgqUepo3Mm7JD3Fpe5jMHMYA45hA"}' + string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567","value":"Wk4en835MZFXsTXM5LD7LjsPuh8TuojptmacO2vevLIs9ut-I6GzgqE9UShiiJw_Rf4GWPYv18LKgk2WE_vL2g6Ftr7LJO5hGNHOyH1e4GW4b4HXRQc4Wu49swDAdVKzWYx77Nqtu8T9XxefzWB7H9kqmT8Dl2PrMxW1cqzAvX3_57OQKLNpVMQukKHJegmlCCt63WJOML_ffILtqiMGQm7du5rM2qp9X9VTW5fR9rae6BRcDAchNFtG2DgCYfcJ9mbe5dXaY0o_actSMbNyjAQQSqiAcYOYdnXgKvp4jOBg_E-dUfJnvBkqesVf6VNfDp13mUKgqxN6UfssWv56HQ"}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -93,18 +146,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: !!python/unicode '{"alg": "RSA-OAEP", "value": "X0G24oDAbZuZdMSUHu8Blr0xdV0ba1qqmgzjRmpKPz6tckCacpET0tcOdM0f8McEj-D7B_jou6yx9P81RWsaLHqkLa8K29sZgSKIh3rcoHDmf7snXgWFoRVX5lfbCJsii2hMrGYKeaLNS2tNFaqbhVlZ34SNUL3YOsQ4b-dLzJ8QQblG-5EQlhPnjcCAHXloC_hM8QMw8EmmMZFALPTuro1MLlnPkSnVxjnyR__vU4SqBRN1fDKHSw-nY1vsWEzQCx38TDkSibV52Qhic4IWXH9wWzKAFRq0NYgra5VD6N_beOzNiUiSM9g_3ltgqUepo3Mm7JD3Fpe5jMHMYA45hA"}' + body: !!python/unicode '{"alg": "RSA-OAEP", "value": "Wk4en835MZFXsTXM5LD7LjsPuh8TuojptmacO2vevLIs9ut-I6GzgqE9UShiiJw_Rf4GWPYv18LKgk2WE_vL2g6Ftr7LJO5hGNHOyH1e4GW4b4HXRQc4Wu49swDAdVKzWYx77Nqtu8T9XxefzWB7H9kqmT8Dl2PrMxW1cqzAvX3_57OQKLNpVMQukKHJegmlCCt63WJOML_ffILtqiMGQm7du5rM2qp9X9VTW5fR9rae6BRcDAchNFtG2DgCYfcJ9mbe5dXaY0o_actSMbNyjAQQSqiAcYOYdnXgKvp4jOBg_E-dUfJnvBkqesVf6VNfDp13mUKgqxN6UfssWv56HQ"}' headers: Accept: - application/json @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/unwrapkey?api-version=7.0 response: body: - string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' + string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11/wrapkey?api-version=7.0 + uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567/wrapkey?api-version=7.0 response: body: - string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11","value":"Vd_kpX6PEPKkMhDfKCcOdq_0TW9G33w-XpreU4mr-cfilx9I5mWF-qUPHrmJmIJZzX6n3HtB3le9Hzn01e0A6EKUKZHA9ZwQaUHzN0jMlQ9UgcXYByxLYPjxIpFIZKrA9F5Ib06vdhVsark7VCeRvzk_3nkpn7qzM2AIH40JgmCTBoIeCGhQ9Zq5nDX9gjKcQoAHC_PtGmD2O6jpbrwq-Yj7tjYsGLBR5wwkr0ooT78mh-Cna3Sr3SYlIBlzZtQepVntzPQl5kV_uqRHQTwnJNFm2sItgk78bsEbKM8IF6OvGmlVBnRdjnKjPr5XLIhAGMFKoH4FV4W9rpakEGbrTQ"}' + string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567","value":"B90rJHfATEFRUcYOZxrAsz18KG9rJNv_oKZeNtbgdeaKE3JMoh_7yv4ljnwG4dK9hokX2uzE2EqQHPZVFGqF6tn8pLYIgk7ZRrVCWiZqlIA--U4VPFti7xPKCudUkX3s3IiQHAaym50nXzCQmJ8m7E1ZiyPtF4KfBAazGmJP4xWVrqBYFtOeuIgfk16Jr2LfSvfHpVJ43K6CYcDkGhCmXxfeLvv4Ypu6I-vVyLXBwf4nCCDPFGbdPZjEERJ73_o4QAP3_xnxOT1Co08nwvxJlD2M-pwqYL5WWXDbINeP1BfbcqcIB33bnIBHuqmGkiy8SDiOapC-v-VRiYcIoJTiAg"}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:59 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -197,18 +250,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: !!python/unicode '{"alg": "RSA-OAEP", "value": "Vd_kpX6PEPKkMhDfKCcOdq_0TW9G33w-XpreU4mr-cfilx9I5mWF-qUPHrmJmIJZzX6n3HtB3le9Hzn01e0A6EKUKZHA9ZwQaUHzN0jMlQ9UgcXYByxLYPjxIpFIZKrA9F5Ib06vdhVsark7VCeRvzk_3nkpn7qzM2AIH40JgmCTBoIeCGhQ9Zq5nDX9gjKcQoAHC_PtGmD2O6jpbrwq-Yj7tjYsGLBR5wwkr0ooT78mh-Cna3Sr3SYlIBlzZtQepVntzPQl5kV_uqRHQTwnJNFm2sItgk78bsEbKM8IF6OvGmlVBnRdjnKjPr5XLIhAGMFKoH4FV4W9rpakEGbrTQ"}' + body: !!python/unicode '{"alg": "RSA-OAEP", "value": "B90rJHfATEFRUcYOZxrAsz18KG9rJNv_oKZeNtbgdeaKE3JMoh_7yv4ljnwG4dK9hokX2uzE2EqQHPZVFGqF6tn8pLYIgk7ZRrVCWiZqlIA--U4VPFti7xPKCudUkX3s3IiQHAaym50nXzCQmJ8m7E1ZiyPtF4KfBAazGmJP4xWVrqBYFtOeuIgfk16Jr2LfSvfHpVJ43K6CYcDkGhCmXxfeLvv4Ypu6I-vVyLXBwf4nCCDPFGbdPZjEERJ73_o4QAP3_xnxOT1Co08nwvxJlD2M-pwqYL5WWXDbINeP1BfbcqcIB33bnIBHuqmGkiy8SDiOapC-v-VRiYcIoJTiAg"}' headers: Accept: - application/json @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11/unwrapkey?api-version=7.0 + uri: https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567/unwrapkey?api-version=7.0 response: body: - string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/31042ce8b51e451cbc6135a866e30d11","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' + string: !!python/unicode '{"kid":"https://vault51cf1084.vault.azure.net/keys/keywrap51cf1084/651fc59e35304c8494e08cb0fb7fe567","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:59 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_deleted_keys.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_deleted_keys.yaml index 34574100b7f9..8a544bcd9e25 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_deleted_keys.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_deleted_keys.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null headers: Accept: - application/json @@ -9,25 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '14' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault30c90fa1.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/4694b581c1034229ba20ecc44d2b2901","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1Gw4yYcU4JK-3gYgDhgjAaOmfxGzXwqAF1FsgYUrjj8I7genL4ixYwDQ7B9ARBzHtjJTKUjmXo9B-5WK7rFnuGvapv5jrb4OT2bcQaEdDynxU6ZdZ6zR20a4AISe6x4UawX7pNboNmEW3GrJcJyNERM0L7lTY6k4B0OomcST7I91hDJtuFmKXvbEfryoRSUPWxsZaZbafvII8IQc2aMHvFdvWtjtuAqVip_DKMq6AgnPjOdPzIvRoUiGkn02YYgy1IRIb7xPmNF6zDKODtFnJMrbLBfSt_e9xIsUN6DVowZNORh-VODRXMbZU1IfPukOb7DPPs3BmRffqZmjCQijTQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545751,"updated":1560545751,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode headers: cache-control: - no-cache content-length: - - '652' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Fri, 14 Jun 2019 20:55:51 GMT + - Tue, 09 Jul 2019 20:21:48 GMT expires: - '-1' pragma: @@ -36,23 +34,26 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -65,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key1/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/7a8e98f209ee4549a5fdef020ffb345e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"oTCk8dlbYEgLSOB02hbF95bRoinx29gUGtUIpzlrQlmTdRFj-Vc4iuoRBN4qjSymHpz6IHL-vBXVup1CmgY4kTay1j5bKtYJAsEVeikQb4-iKNZw1uW81se171g1lJm75EPj79z-gigmi0iYzgKsK2pBAtMNC9AtI-IE5bMNdCMHx4Vgr3xeewQ7x_G13BuJcAP6NrNH1Edr-U1cPLPp0NPqyhFBv9FiEJa1AWmpy-wqeDA81K4zX1XWDTZgBWxojBMRLS4BZZMe4yLCjo-LE66Pxfdro82JwFGn7FQqzVmnseGSjx8_hGX_gPhQDiyptq3sBq3R6NQNxKt3HGBl7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/66e092cadb584486b58b53afa6372d78","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j4p-OxeNt1FbUunWzm3Kkb9Ytl8mR2kWC1--XOAQuN8P3QPWcEz525hE2rTPu37QUuEE_LvYmtLTfK7KILggA93ZcHJb2RoR6u8YhV4x6dhrSN5NYQljtI2C1yyoomO9BjI_T5MIV2lOsKXOeTjgzk9tKk5de0mfQdEP16s1-15Z_VBWzhFpdPMU3R7wR2XhRpe0js4101kDsiMpslwdsQ57AtKsD6qgyPk_myshxnyLmgQErjOlZMVxEBNdsB5DKjG9yDIIuWY8-z0Rb5IV5gUwsJdBBm0QvydsBimqHVckGoKNBgN36m9XAhnFPzaLlIT6p7l8wSlVfkXo0NKc2w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:52 GMT + - Tue, 09 Jul 2019 20:21:49 GMT expires: - '-1' pragma: @@ -93,18 +94,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -117,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key2/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/1edb8703c04543b2ac19d7ee83e8e899","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMzKfODSRDwQGMAYvfIAocABznRAYbRI48IV5Kd0F_LJnJnaY8uoF_ZbXdJKo8BHdqdzO8Kz7s9lYTD1Xh4sso_fZea5oBBnuRuvXAcU1KxO1qWsQDYMxEh-zJXRP-kH9gNvB2avX2aOvoPBhx1TyVvX3ekDNLaN2zouWptWAT-izgjTXesFnucUsCN09jGAQruTmR3JiD-v_9-9oWjtJHTMLUOcFa0Vfu9xfye8fPaIaQPRN4PRiNfP8Ks_pWOVebOCkKziT9LFee_b03hyOay2DvqZYuiwsmT06zo1D5tjPZflHTm5ho4Tvy-_-oFEwOEaLARZAc2-pZ_MHYvVhQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/902521f7ecde43249f274dc7d8be3f0b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"7vD4k5XuTpBTTZDI_ZagDd5-ZpCV1EU5bNsxA18X5uvd6N6emQ8M22dTmpd3LKF80St3198z83wMK_VgHj9dHxmBxu2dfSuM3ETEiAIJDnMJja1fmL3D9ehXYnBkmKQgjlq4x-VMDjq-2hiqVAq8Hcz8i8dfDd3fBjpd3nDRJR5qH8hU6IpOrdtsWBYnu4KKlAvODhSo7hC-pDn443bf-noRzGhuJacG-IcNb2oc-m_EBOhWJcFByLZZZZUjgUTj_K62KSaWLCND_z7x01f8Wq_p48s_2AfDnpiWWrTVfhMZjcVyC6Cn66qM3z89TSNpyEt7FXk4sgfVlhd6EqX4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:52 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -145,18 +146,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -169,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key3/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/65f194b2ffde4ee28886ebabf972302d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0A6Ws9QXysI-vpIlNpOjTSj7XCbUkDbm75AxWV4EWSCZIqPhgIIvS6B0I0x0JQThlh34z_qW3HHtDZFL-jFt_T_VEVVWzTou9aq0QxOU2GuYO9UALfzsvwNgsqNlsjDWUXVJmzMXa3TEE38Bb0jrID9y9GCqlOtb1xRdFKsX-sG7UPr0GfB8U4lfcuZG1YshXfTf-b3BEUlbnH1P2cyKzyzkbkV6k263By53LhWgt9WT154FdH9YRvMZ1U7iPscL7DMJdoCoI8PbeGxFRsFJbL1W46Fbz80vMifSuXIaM-LD3jTa39NgsqYAMBPDGheIbGSooqwj_CpSf1OCVJFoRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/c24a07a9f7704d02a0b678ee58a69f6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"5-71tRmF0rBJIIXT95phahUd9w0IyvfxfeEoOu3Q8Gk9cGeRdk4CsMYbDdMjxnY_bznqQzBRTAHo4jY-P5rwwHsEXdJHbB5GoiefT2J78cq9cUfpI0k0vWYDlOAxT_t1ReiXwvDi96jYsrLOnyTbRe6289ClcyQdu9ZdkRJKIWMoFYifJnH3lv5Aj-BlmoOLCjf0CV4QTIoQ9T2--PUM983cdfASmWOM7AbAsscWQsNpelrQ14fOwWBah_m6evDkumDlghTcvlPZP3sZuQ8gVQObRGe6EJ3ZJRyzVScpXTWemxSWHNdy1qgale0qLYm7pRgmQ7ov69y4hURsF563yQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:52 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -197,18 +198,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -221,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key4/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/372b300d46be4b5f9f7b229a0b5cae3f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1HNs0b0GCoJO5uDkISYnRnPR7huYFdqPt9T-tpUNu9qYXM6U9A1gasPM0eQVTmQXwvNdvykFIEsEfwsIk4Lh75JeGHMng3nEkD0Fsw5l898L8R7vohv3ZMJKVfrSnj3gz0FEJ2Y5o2KzeDlmOrjrFEO9WiBImzWHs47DlKpC1NuQpp6kKpeHKMyF0TdtdvggrMFzj51QiMq4I2e-6mjXhSc-CI5Nn4SzuKGkeWfyk_j05FZrgpNzou6v9aHnpf4AQ7Cka-xpN8GxmaCM25v729LOACPa012MzuxMFwpK7hIqqrOERrD9bJbpZkJt0UdwCeyrm-z1Q8Rro1Ue390_Sw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/60c8c00601a549ee931e310071707726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s7MHUKi4NPRauqusKvUQwwkT7fZg-7SPIngAKsNzMtup8G1fmqYeeA9gZIgEsv-LPx_QbaH7xRlx2SFEy8O8bV3l21dTUPpNzxnoYn2POhehVIf9UR6ciS2JLWKaHrMoVeGMxXo29-nXNQcO45vDA4xwdWm8jI41seJ4aHTORMpyldtSWF0kdAec16kVb3JG1-AGQNhnfkW4BSTTP3bmLIzj5e42Sz_7t_EaQa8GktThHUkOswh_XdBBzMcsjKe05sgpnBQsV5DHdyDTWDac036Pkibr5NIdmjQW7l_CLhC1H8HGqMfs8SVMWFqfFE3ieWJkHEHAb0IQz3HgyOddPw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:52 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -249,18 +250,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -273,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key5/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/86b7ab07bff349f9be68a7a9307a471d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wIsIxpIBwunu_FdctlAXrlnz5DQNf2iiVn8JpBTCaYeuZqwIPwQawBhQtVBE73waomHEC5KpGoZtJj7kZwFGJXi-KdgInglecgMw-cmlxzF_zRi0CXyNofZGmf_QTUJODEm5C3h-P5HBh7cirIjmKc5tzesrXL0Akf6QEWW7XZn_gJsZxOjN5KAkRJGbk9IlNuaRWgJflgydnOaVeyIVHqpXncVX00DWDGHoyOXbtLaF52mZXgX7c3ef92MwrYuuY_zFsQqru0dAsIlxFs-MVf7csrIIB2z3U3AtZRHBHjWb78OkwqdlIxCINi6ODL0g3oe3iKzsIIvSp_M-Pz8DLw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/3fd8b8ffaf014d5f8467b18ec4f4d738","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1l78UTmqwfLH49oPpljiz3vcFhEBH9hz2XoiKEprHB-VC3D9lNI-8sDn7CJQSjToSAOOWcpg3oZQPLVqlrive9G5Sx9fKWkrsWDLt8cmgGgi9iRZ8cXaijWfC539vSd6odMj8MpvyPa6DIMxuUh8glrz8i9vWKfiONzjzLkh5M4kuw_H2-r0OFzoP2VwWHSQXLfTWv5mMuO-hH1NCaqmuBpAAwHpZufAGzZr4GlrL9sRZiSVXrmX-n_7v8CpSIge-lgB_SbTHNf_Wnr0D-9yy9UKTsDpTNB0F3XaPlFBsu3-NkbNyhikyKG8CaiG_ecmyunFgoS17FI-hXeKH5Bvjw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:53 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -301,18 +302,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -325,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault30c90fa1.vault.azure.net/keys/key6/create?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/16d98d0d39704ca2bd121ddd27812078","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zIg0SGg1FDAGfsTlhbVGNiEKe8VOgIiOr0Oeb9uAD1PD4wVVC1dA7wHdLagN7j84ReS0sDQIYREhkh42jErXt_GryvMBriXgCyll59gnJkmf2CgJh_uqNifaOIlcLK8nlGmglbicyGyH8RSkE1whWB62LuQsIB_NY3JikIYhzU4rmGYwK1ibZ3Eyfeyi_mlo8MHY4DAjz9qDuJG_E6WlS0iTrv1HGJp322ut6lJLg-PJVvC1VxWJio8YkYw-eoJct4wQ1HqqOTCWaccrYHb5bhIF0SNxW3V5XBsGwLC_Hnzt290InNgvD_EYAynzcDcsKhsKDUwl_enIcsbx-DsnQw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/9448590c5c9c444c927499bf4f095adb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"niiseVJzh42O1HbWrrHuQm0Vk_xEPWLeFgsKr8BRcfCtAPlbm9hNt8dUfCTmVi6SKCM2BsxKt5i9w1CB57XeP2p5RtjrWJ4etXOlgiPezS0MNEdRmOtdUWc0N0ZAtie5lHYGcnWxcUGVXRWCw7z0Iddguw2p8gGunUbs6okERJEaTrm3sTYmH-ENzrlIuDyQDUxoSu36lNm6M4NL5iw7w7VdVYQCIrCf6SsInj6A_DF15ftzk4Bg9sKey8Tw7FxLfK81B1QbUAVE-BJnrwrej7laAfKAQ6dmvssmkG0lE-nh8vU4KLCJQh-mpZ5VLou2086KPoqvRm--PdsULyh4IQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:53 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -353,18 +354,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -373,23 +374,25 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '14' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key0?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault30c90fa1.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/4694b581c1034229ba20ecc44d2b2901","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1Gw4yYcU4JK-3gYgDhgjAaOmfxGzXwqAF1FsgYUrjj8I7genL4ixYwDQ7B9ARBzHtjJTKUjmXo9B-5WK7rFnuGvapv5jrb4OT2bcQaEdDynxU6ZdZ6zR20a4AISe6x4UawX7pNboNmEW3GrJcJyNERM0L7lTY6k4B0OomcST7I91hDJtuFmKXvbEfryoRSUPWxsZaZbafvII8IQc2aMHvFdvWtjtuAqVip_DKMq6AgnPjOdPzIvRoUiGkn02YYgy1IRIb7xPmNF6zDKODtFnJMrbLBfSt_e9xIsUN6DVowZNORh-VODRXMbZU1IfPukOb7DPPs3BmRffqZmjCQijTQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545751,"updated":1560545751,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/0caa535292e841329ae26218ad5f2734","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nioBB92E3S9bL3DX6rAh_OeoDlNR2_YQz8gOUn3sS-oVoJ23LCZF-Ery9e7kDXuUNDOqK4XG1XuIa9umnBohreby8_dhBDgaFHeU3lbtWRGROw930MGT05Cjgk2lZx3BCnuQU6Zie60s7a7uLmjMdm8Uy5lwuoPVOMRMASOadirg44oHXtbEdzmrjalY5Nq4zTE6e9LFqPGJDchwotfFJAchFbd2z5lxyqfuk6NRALtrFG3SigDyGRKJC0OgO5Q7SREmx6Xdsp_1Dq_0tRnYxGYOuNKWsjXP9PyJQsYquI0gz2hBEaPmrlvBHsuLFTG3JQv1ULIzRyvn53vUiSbDlw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '779' + - '652' content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:53 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -403,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -425,12 +428,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key1?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/7a8e98f209ee4549a5fdef020ffb345e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"oTCk8dlbYEgLSOB02hbF95bRoinx29gUGtUIpzlrQlmTdRFj-Vc4iuoRBN4qjSymHpz6IHL-vBXVup1CmgY4kTay1j5bKtYJAsEVeikQb4-iKNZw1uW81se171g1lJm75EPj79z-gigmi0iYzgKsK2pBAtMNC9AtI-IE5bMNdCMHx4Vgr3xeewQ7x_G13BuJcAP6NrNH1Edr-U1cPLPp0NPqyhFBv9FiEJa1AWmpy-wqeDA81K4zX1XWDTZgBWxojBMRLS4BZZMe4yLCjo-LE66Pxfdro82JwFGn7FQqzVmnseGSjx8_hGX_gPhQDiyptq3sBq3R6NQNxKt3HGBl7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/60c8c00601a549ee931e310071707726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s7MHUKi4NPRauqusKvUQwwkT7fZg-7SPIngAKsNzMtup8G1fmqYeeA9gZIgEsv-LPx_QbaH7xRlx2SFEy8O8bV3l21dTUPpNzxnoYn2POhehVIf9UR6ciS2JLWKaHrMoVeGMxXo29-nXNQcO45vDA4xwdWm8jI41seJ4aHTORMpyldtSWF0kdAec16kVb3JG1-AGQNhnfkW4BSTTP3bmLIzj5e42Sz_7t_EaQa8GktThHUkOswh_XdBBzMcsjKe05sgpnBQsV5DHdyDTWDac036Pkibr5NIdmjQW7l_CLhC1H8HGqMfs8SVMWFqfFE3ieWJkHEHAb0IQz3HgyOddPw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -439,7 +442,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:53 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -453,11 +456,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -475,12 +478,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault30c90fa1.vault.azure.net/keys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/1edb8703c04543b2ac19d7ee83e8e899","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMzKfODSRDwQGMAYvfIAocABznRAYbRI48IV5Kd0F_LJnJnaY8uoF_ZbXdJKo8BHdqdzO8Kz7s9lYTD1Xh4sso_fZea5oBBnuRuvXAcU1KxO1qWsQDYMxEh-zJXRP-kH9gNvB2avX2aOvoPBhx1TyVvX3ekDNLaN2zouWptWAT-izgjTXesFnucUsCN09jGAQruTmR3JiD-v_9-9oWjtJHTMLUOcFa0Vfu9xfye8fPaIaQPRN4PRiNfP8Ks_pWOVebOCkKziT9LFee_b03hyOay2DvqZYuiwsmT06zo1D5tjPZflHTm5ho4Tvy-_-oFEwOEaLARZAc2-pZ_MHYvVhQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/c24a07a9f7704d02a0b678ee58a69f6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"5-71tRmF0rBJIIXT95phahUd9w0IyvfxfeEoOu3Q8Gk9cGeRdk4CsMYbDdMjxnY_bznqQzBRTAHo4jY-P5rwwHsEXdJHbB5GoiefT2J78cq9cUfpI0k0vWYDlOAxT_t1ReiXwvDi96jYsrLOnyTbRe6289ClcyQdu9ZdkRJKIWMoFYifJnH3lv5Aj-BlmoOLCjf0CV4QTIoQ9T2--PUM983cdfASmWOM7AbAsscWQsNpelrQ14fOwWBah_m6evDkumDlghTcvlPZP3sZuQ8gVQObRGe6EJ3ZJRyzVScpXTWemxSWHNdy1qgale0qLYm7pRgmQ7ov69y4hURsF563yQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -489,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:53 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -503,11 +506,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -525,12 +528,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key3?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/65f194b2ffde4ee28886ebabf972302d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0A6Ws9QXysI-vpIlNpOjTSj7XCbUkDbm75AxWV4EWSCZIqPhgIIvS6B0I0x0JQThlh34z_qW3HHtDZFL-jFt_T_VEVVWzTou9aq0QxOU2GuYO9UALfzsvwNgsqNlsjDWUXVJmzMXa3TEE38Bb0jrID9y9GCqlOtb1xRdFKsX-sG7UPr0GfB8U4lfcuZG1YshXfTf-b3BEUlbnH1P2cyKzyzkbkV6k263By53LhWgt9WT154FdH9YRvMZ1U7iPscL7DMJdoCoI8PbeGxFRsFJbL1W46Fbz80vMifSuXIaM-LD3jTa39NgsqYAMBPDGheIbGSooqwj_CpSf1OCVJFoRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/902521f7ecde43249f274dc7d8be3f0b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"7vD4k5XuTpBTTZDI_ZagDd5-ZpCV1EU5bNsxA18X5uvd6N6emQ8M22dTmpd3LKF80St3198z83wMK_VgHj9dHxmBxu2dfSuM3ETEiAIJDnMJja1fmL3D9ehXYnBkmKQgjlq4x-VMDjq-2hiqVAq8Hcz8i8dfDd3fBjpd3nDRJR5qH8hU6IpOrdtsWBYnu4KKlAvODhSo7hC-pDn443bf-noRzGhuJacG-IcNb2oc-m_EBOhWJcFByLZZZZUjgUTj_K62KSaWLCND_z7x01f8Wq_p48s_2AfDnpiWWrTVfhMZjcVyC6Cn66qM3z89TSNpyEt7FXk4sgfVlhd6EqX4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -539,7 +542,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:54 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -553,11 +556,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -575,12 +578,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key4?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/372b300d46be4b5f9f7b229a0b5cae3f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1HNs0b0GCoJO5uDkISYnRnPR7huYFdqPt9T-tpUNu9qYXM6U9A1gasPM0eQVTmQXwvNdvykFIEsEfwsIk4Lh75JeGHMng3nEkD0Fsw5l898L8R7vohv3ZMJKVfrSnj3gz0FEJ2Y5o2KzeDlmOrjrFEO9WiBImzWHs47DlKpC1NuQpp6kKpeHKMyF0TdtdvggrMFzj51QiMq4I2e-6mjXhSc-CI5Nn4SzuKGkeWfyk_j05FZrgpNzou6v9aHnpf4AQ7Cka-xpN8GxmaCM25v729LOACPa012MzuxMFwpK7hIqqrOERrD9bJbpZkJt0UdwCeyrm-z1Q8Rro1Ue390_Sw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/66e092cadb584486b58b53afa6372d78","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j4p-OxeNt1FbUunWzm3Kkb9Ytl8mR2kWC1--XOAQuN8P3QPWcEz525hE2rTPu37QUuEE_LvYmtLTfK7KILggA93ZcHJb2RoR6u8YhV4x6dhrSN5NYQljtI2C1yyoomO9BjI_T5MIV2lOsKXOeTjgzk9tKk5de0mfQdEP16s1-15Z_VBWzhFpdPMU3R7wR2XhRpe0js4101kDsiMpslwdsQ57AtKsD6qgyPk_myshxnyLmgQErjOlZMVxEBNdsB5DKjG9yDIIuWY8-z0Rb5IV5gUwsJdBBm0QvydsBimqHVckGoKNBgN36m9XAhnFPzaLlIT6p7l8wSlVfkXo0NKc2w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -589,7 +592,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:54 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -603,11 +606,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -625,12 +628,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key5?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/86b7ab07bff349f9be68a7a9307a471d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wIsIxpIBwunu_FdctlAXrlnz5DQNf2iiVn8JpBTCaYeuZqwIPwQawBhQtVBE73waomHEC5KpGoZtJj7kZwFGJXi-KdgInglecgMw-cmlxzF_zRi0CXyNofZGmf_QTUJODEm5C3h-P5HBh7cirIjmKc5tzesrXL0Akf6QEWW7XZn_gJsZxOjN5KAkRJGbk9IlNuaRWgJflgydnOaVeyIVHqpXncVX00DWDGHoyOXbtLaF52mZXgX7c3ef92MwrYuuY_zFsQqru0dAsIlxFs-MVf7csrIIB2z3U3AtZRHBHjWb78OkwqdlIxCINi6ODL0g3oe3iKzsIIvSp_M-Pz8DLw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/0caa535292e841329ae26218ad5f2734","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nioBB92E3S9bL3DX6rAh_OeoDlNR2_YQz8gOUn3sS-oVoJ23LCZF-Ery9e7kDXuUNDOqK4XG1XuIa9umnBohreby8_dhBDgaFHeU3lbtWRGROw930MGT05Cjgk2lZx3BCnuQU6Zie60s7a7uLmjMdm8Uy5lwuoPVOMRMASOadirg44oHXtbEdzmrjalY5Nq4zTE6e9LFqPGJDchwotfFJAchFbd2z5lxyqfuk6NRALtrFG3SigDyGRKJC0OgO5Q7SREmx6Xdsp_1Dq_0tRnYxGYOuNKWsjXP9PyJQsYquI0gz2hBEaPmrlvBHsuLFTG3JQv1ULIzRyvn53vUiSbDlw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -639,7 +642,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:54 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -653,11 +656,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -675,12 +678,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault30c90fa1.vault.azure.net/keys/key6?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/keys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/16d98d0d39704ca2bd121ddd27812078","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zIg0SGg1FDAGfsTlhbVGNiEKe8VOgIiOr0Oeb9uAD1PD4wVVC1dA7wHdLagN7j84ReS0sDQIYREhkh42jErXt_GryvMBriXgCyll59gnJkmf2CgJh_uqNifaOIlcLK8nlGmglbicyGyH8RSkE1whWB62LuQsIB_NY3JikIYhzU4rmGYwK1ibZ3Eyfeyi_mlo8MHY4DAjz9qDuJG_E6WlS0iTrv1HGJp322ut6lJLg-PJVvC1VxWJio8YkYw-eoJct4wQ1HqqOTCWaccrYHb5bhIF0SNxW3V5XBsGwLC_Hnzt290InNgvD_EYAynzcDcsKhsKDUwl_enIcsbx-DsnQw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/9448590c5c9c444c927499bf4f095adb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"niiseVJzh42O1HbWrrHuQm0Vk_xEPWLeFgsKr8BRcfCtAPlbm9hNt8dUfCTmVi6SKCM2BsxKt5i9w1CB57XeP2p5RtjrWJ4etXOlgiPezS0MNEdRmOtdUWc0N0ZAtie5lHYGcnWxcUGVXRWCw7z0Iddguw2p8gGunUbs6okERJEaTrm3sTYmH-ENzrlIuDyQDUxoSu36lNm6M4NL5iw7w7VdVYQCIrCf6SsInj6A_DF15ftzk4Bg9sKey8Tw7FxLfK81B1QbUAVE-BJnrwrej7laAfKAQ6dmvssmkG0lE-nh8vU4KLCJQh-mpZ5VLou2086KPoqvRm--PdsULyh4IQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -689,7 +692,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:55:54 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -703,11 +706,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -722,118 +725,24 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' - headers: - cache-control: - - no-cache - content-length: - - '72' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 14 Jun 2019 20:55:54 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' - headers: - cache-control: - - no-cache - content-length: - - '72' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 14 Jun 2019 20:55:57 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault30c90fa1.vault.azure.net/keys/key4?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/3fd8b8ffaf014d5f8467b18ec4f4d738","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1l78UTmqwfLH49oPpljiz3vcFhEBH9hz2XoiKEprHB-VC3D9lNI-8sDn7CJQSjToSAOOWcpg3oZQPLVqlrive9G5Sx9fKWkrsWDLt8cmgGgi9iRZ8cXaijWfC539vSd6odMj8MpvyPa6DIMxuUh8glrz8i9vWKfiONzjzLkh5M4kuw_H2-r0OFzoP2VwWHSQXLfTWv5mMuO-hH1NCaqmuBpAAwHpZufAGzZr4GlrL9sRZiSVXrmX-n_7v8CpSIge-lgB_SbTHNf_Wnr0D-9yy9UKTsDpTNB0F3XaPlFBsu3-NkbNyhikyKG8CaiG_ecmyunFgoS17FI-hXeKH5Bvjw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:00 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -847,16 +756,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -867,12 +776,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -881,7 +791,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:03 GMT + - Tue, 09 Jul 2019 20:21:54 GMT expires: - '-1' pragma: @@ -895,11 +805,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -915,12 +825,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -929,7 +840,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:06 GMT + - Tue, 09 Jul 2019 20:21:57 GMT expires: - '-1' pragma: @@ -943,11 +854,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -963,12 +874,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -977,7 +889,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:09 GMT + - Tue, 09 Jul 2019 20:22:00 GMT expires: - '-1' pragma: @@ -991,11 +903,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1011,12 +923,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -1025,7 +938,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:12 GMT + - Tue, 09 Jul 2019 20:22:03 GMT expires: - '-1' pragma: @@ -1039,11 +952,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1059,12 +972,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/4694b581c1034229ba20ecc44d2b2901","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1Gw4yYcU4JK-3gYgDhgjAaOmfxGzXwqAF1FsgYUrjj8I7genL4ixYwDQ7B9ARBzHtjJTKUjmXo9B-5WK7rFnuGvapv5jrb4OT2bcQaEdDynxU6ZdZ6zR20a4AISe6x4UawX7pNboNmEW3GrJcJyNERM0L7lTY6k4B0OomcST7I91hDJtuFmKXvbEfryoRSUPWxsZaZbafvII8IQc2aMHvFdvWtjtuAqVip_DKMq6AgnPjOdPzIvRoUiGkn02YYgy1IRIb7xPmNF6zDKODtFnJMrbLBfSt_e9xIsUN6DVowZNORh-VODRXMbZU1IfPukOb7DPPs3BmRffqZmjCQijTQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545751,"updated":1560545751,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/60c8c00601a549ee931e310071707726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s7MHUKi4NPRauqusKvUQwwkT7fZg-7SPIngAKsNzMtup8G1fmqYeeA9gZIgEsv-LPx_QbaH7xRlx2SFEy8O8bV3l21dTUPpNzxnoYn2POhehVIf9UR6ciS2JLWKaHrMoVeGMxXo29-nXNQcO45vDA4xwdWm8jI41seJ4aHTORMpyldtSWF0kdAec16kVb3JG1-AGQNhnfkW4BSTTP3bmLIzj5e42Sz_7t_EaQa8GktThHUkOswh_XdBBzMcsjKe05sgpnBQsV5DHdyDTWDac036Pkibr5NIdmjQW7l_CLhC1H8HGqMfs8SVMWFqfFE3ieWJkHEHAb0IQz3HgyOddPw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1073,7 +986,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:15 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1087,11 +1000,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1107,12 +1020,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key1?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/7a8e98f209ee4549a5fdef020ffb345e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"oTCk8dlbYEgLSOB02hbF95bRoinx29gUGtUIpzlrQlmTdRFj-Vc4iuoRBN4qjSymHpz6IHL-vBXVup1CmgY4kTay1j5bKtYJAsEVeikQb4-iKNZw1uW81se171g1lJm75EPj79z-gigmi0iYzgKsK2pBAtMNC9AtI-IE5bMNdCMHx4Vgr3xeewQ7x_G13BuJcAP6NrNH1Edr-U1cPLPp0NPqyhFBv9FiEJa1AWmpy-wqeDA81K4zX1XWDTZgBWxojBMRLS4BZZMe4yLCjo-LE66Pxfdro82JwFGn7FQqzVmnseGSjx8_hGX_gPhQDiyptq3sBq3R6NQNxKt3HGBl7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/c24a07a9f7704d02a0b678ee58a69f6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"5-71tRmF0rBJIIXT95phahUd9w0IyvfxfeEoOu3Q8Gk9cGeRdk4CsMYbDdMjxnY_bznqQzBRTAHo4jY-P5rwwHsEXdJHbB5GoiefT2J78cq9cUfpI0k0vWYDlOAxT_t1ReiXwvDi96jYsrLOnyTbRe6289ClcyQdu9ZdkRJKIWMoFYifJnH3lv5Aj-BlmoOLCjf0CV4QTIoQ9T2--PUM983cdfASmWOM7AbAsscWQsNpelrQ14fOwWBah_m6evDkumDlghTcvlPZP3sZuQ8gVQObRGe6EJ3ZJRyzVScpXTWemxSWHNdy1qgale0qLYm7pRgmQ7ov69y4hURsF563yQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1121,7 +1034,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1135,11 +1048,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1155,12 +1068,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key2?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key2/1edb8703c04543b2ac19d7ee83e8e899","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMzKfODSRDwQGMAYvfIAocABznRAYbRI48IV5Kd0F_LJnJnaY8uoF_ZbXdJKo8BHdqdzO8Kz7s9lYTD1Xh4sso_fZea5oBBnuRuvXAcU1KxO1qWsQDYMxEh-zJXRP-kH9gNvB2avX2aOvoPBhx1TyVvX3ekDNLaN2zouWptWAT-izgjTXesFnucUsCN09jGAQruTmR3JiD-v_9-9oWjtJHTMLUOcFa0Vfu9xfye8fPaIaQPRN4PRiNfP8Ks_pWOVebOCkKziT9LFee_b03hyOay2DvqZYuiwsmT06zo1D5tjPZflHTm5ho4Tvy-_-oFEwOEaLARZAc2-pZ_MHYvVhQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key1/902521f7ecde43249f274dc7d8be3f0b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"7vD4k5XuTpBTTZDI_ZagDd5-ZpCV1EU5bNsxA18X5uvd6N6emQ8M22dTmpd3LKF80St3198z83wMK_VgHj9dHxmBxu2dfSuM3ETEiAIJDnMJja1fmL3D9ehXYnBkmKQgjlq4x-VMDjq-2hiqVAq8Hcz8i8dfDd3fBjpd3nDRJR5qH8hU6IpOrdtsWBYnu4KKlAvODhSo7hC-pDn443bf-noRzGhuJacG-IcNb2oc-m_EBOhWJcFByLZZZZUjgUTj_K62KSaWLCND_z7x01f8Wq_p48s_2AfDnpiWWrTVfhMZjcVyC6Cn66qM3z89TSNpyEt7FXk4sgfVlhd6EqX4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1169,7 +1082,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1183,11 +1096,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1203,12 +1116,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key3?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key3/65f194b2ffde4ee28886ebabf972302d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0A6Ws9QXysI-vpIlNpOjTSj7XCbUkDbm75AxWV4EWSCZIqPhgIIvS6B0I0x0JQThlh34z_qW3HHtDZFL-jFt_T_VEVVWzTou9aq0QxOU2GuYO9UALfzsvwNgsqNlsjDWUXVJmzMXa3TEE38Bb0jrID9y9GCqlOtb1xRdFKsX-sG7UPr0GfB8U4lfcuZG1YshXfTf-b3BEUlbnH1P2cyKzyzkbkV6k263By53LhWgt9WT154FdH9YRvMZ1U7iPscL7DMJdoCoI8PbeGxFRsFJbL1W46Fbz80vMifSuXIaM-LD3jTa39NgsqYAMBPDGheIbGSooqwj_CpSf1OCVJFoRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key0/66e092cadb584486b58b53afa6372d78","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j4p-OxeNt1FbUunWzm3Kkb9Ytl8mR2kWC1--XOAQuN8P3QPWcEz525hE2rTPu37QUuEE_LvYmtLTfK7KILggA93ZcHJb2RoR6u8YhV4x6dhrSN5NYQljtI2C1yyoomO9BjI_T5MIV2lOsKXOeTjgzk9tKk5de0mfQdEP16s1-15Z_VBWzhFpdPMU3R7wR2XhRpe0js4101kDsiMpslwdsQ57AtKsD6qgyPk_myshxnyLmgQErjOlZMVxEBNdsB5DKjG9yDIIuWY8-z0Rb5IV5gUwsJdBBm0QvydsBimqHVckGoKNBgN36m9XAhnFPzaLlIT6p7l8wSlVfkXo0NKc2w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1217,7 +1130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1231,11 +1144,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1251,12 +1164,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key4?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/372b300d46be4b5f9f7b229a0b5cae3f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1HNs0b0GCoJO5uDkISYnRnPR7huYFdqPt9T-tpUNu9qYXM6U9A1gasPM0eQVTmQXwvNdvykFIEsEfwsIk4Lh75JeGHMng3nEkD0Fsw5l898L8R7vohv3ZMJKVfrSnj3gz0FEJ2Y5o2KzeDlmOrjrFEO9WiBImzWHs47DlKpC1NuQpp6kKpeHKMyF0TdtdvggrMFzj51QiMq4I2e-6mjXhSc-CI5Nn4SzuKGkeWfyk_j05FZrgpNzou6v9aHnpf4AQ7Cka-xpN8GxmaCM25v729LOACPa012MzuxMFwpK7hIqqrOERrD9bJbpZkJt0UdwCeyrm-z1Q8Rro1Ue390_Sw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/0caa535292e841329ae26218ad5f2734","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nioBB92E3S9bL3DX6rAh_OeoDlNR2_YQz8gOUn3sS-oVoJ23LCZF-Ery9e7kDXuUNDOqK4XG1XuIa9umnBohreby8_dhBDgaFHeU3lbtWRGROw930MGT05Cjgk2lZx3BCnuQU6Zie60s7a7uLmjMdm8Uy5lwuoPVOMRMASOadirg44oHXtbEdzmrjalY5Nq4zTE6e9LFqPGJDchwotfFJAchFbd2z5lxyqfuk6NRALtrFG3SigDyGRKJC0OgO5Q7SREmx6Xdsp_1Dq_0tRnYxGYOuNKWsjXP9PyJQsYquI0gz2hBEaPmrlvBHsuLFTG3JQv1ULIzRyvn53vUiSbDlw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1265,7 +1178,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1279,11 +1192,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1299,12 +1212,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/86b7ab07bff349f9be68a7a9307a471d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wIsIxpIBwunu_FdctlAXrlnz5DQNf2iiVn8JpBTCaYeuZqwIPwQawBhQtVBE73waomHEC5KpGoZtJj7kZwFGJXi-KdgInglecgMw-cmlxzF_zRi0CXyNofZGmf_QTUJODEm5C3h-P5HBh7cirIjmKc5tzesrXL0Akf6QEWW7XZn_gJsZxOjN5KAkRJGbk9IlNuaRWgJflgydnOaVeyIVHqpXncVX00DWDGHoyOXbtLaF52mZXgX7c3ef92MwrYuuY_zFsQqru0dAsIlxFs-MVf7csrIIB2z3U3AtZRHBHjWb78OkwqdlIxCINi6ODL0g3oe3iKzsIIvSp_M-Pz8DLw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key5/9448590c5c9c444c927499bf4f095adb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"niiseVJzh42O1HbWrrHuQm0Vk_xEPWLeFgsKr8BRcfCtAPlbm9hNt8dUfCTmVi6SKCM2BsxKt5i9w1CB57XeP2p5RtjrWJ4etXOlgiPezS0MNEdRmOtdUWc0N0ZAtie5lHYGcnWxcUGVXRWCw7z0Iddguw2p8gGunUbs6okERJEaTrm3sTYmH-ENzrlIuDyQDUxoSu36lNm6M4NL5iw7w7VdVYQCIrCf6SsInj6A_DF15ftzk4Bg9sKey8Tw7FxLfK81B1QbUAVE-BJnrwrej7laAfKAQ6dmvssmkG0lE-nh8vU4KLCJQh-mpZ5VLou2086KPoqvRm--PdsULyh4IQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1313,7 +1226,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1327,11 +1240,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1347,12 +1260,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key6?api-version=7.0 + uri: https://vault30c90fa1.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key6/16d98d0d39704ca2bd121ddd27812078","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zIg0SGg1FDAGfsTlhbVGNiEKe8VOgIiOr0Oeb9uAD1PD4wVVC1dA7wHdLagN7j84ReS0sDQIYREhkh42jErXt_GryvMBriXgCyll59gnJkmf2CgJh_uqNifaOIlcLK8nlGmglbicyGyH8RSkE1whWB62LuQsIB_NY3JikIYhzU4rmGYwK1ibZ3Eyfeyi_mlo8MHY4DAjz9qDuJG_E6WlS0iTrv1HGJp322ut6lJLg-PJVvC1VxWJio8YkYw-eoJct4wQ1HqqOTCWaccrYHb5bhIF0SNxW3V5XBsGwLC_Hnzt290InNgvD_EYAynzcDcsKhsKDUwl_enIcsbx-DsnQw","e":"AQAB"},"attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault30c90fa1.vault.azure.net/keys/key4/3fd8b8ffaf014d5f8467b18ec4f4d738","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1l78UTmqwfLH49oPpljiz3vcFhEBH9hz2XoiKEprHB-VC3D9lNI-8sDn7CJQSjToSAOOWcpg3oZQPLVqlrive9G5Sx9fKWkrsWDLt8cmgGgi9iRZ8cXaijWfC539vSd6odMj8MpvyPa6DIMxuUh8glrz8i9vWKfiONzjzLkh5M4kuw_H2-r0OFzoP2VwWHSQXLfTWv5mMuO-hH1NCaqmuBpAAwHpZufAGzZr4GlrL9sRZiSVXrmX-n_7v8CpSIge-lgB_SbTHNf_Wnr0D-9yy9UKTsDpTNB0F3XaPlFBsu3-NkbNyhikyKG8CaiG_ecmyunFgoS17FI-hXeKH5Bvjw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1361,7 +1274,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1375,11 +1288,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1395,12 +1308,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault30c90fa1.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560545751,"updated":1560545751,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault30c90fa1.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault30c90fa1.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -1409,7 +1322,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1423,11 +1336,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1443,12 +1356,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault30c90fa1.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560545751,"updated":1560545751,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1560545753,"scheduledPurgeDate":1568321753,"kid":"https://vault30c90fa1.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560545752,"updated":1560545752,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1560545754,"scheduledPurgeDate":1568321754,"kid":"https://vault30c90fa1.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560545753,"updated":1560545753,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault30c90fa1.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault30c90fa1.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault30c90fa1.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault30c90fa1.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -1457,7 +1370,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 20:56:16 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1471,11 +1384,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_versions.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_versions.yaml index 2227f4cc7099..cf10c25f2bed 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_versions.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_list_versions.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:21:50 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: !!python/unicode '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/02c57a93dc494ba8a7ab0ae31674eb0c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0KX1ZxiwjkstgscKCOkVMIbC8IMVr83MLhXGAB9_wU2TIrvz3NTym_IGvof4rOiZQQdIHv-6yY8Gt2DxBKMnoBAOjk-ezSI3NLJqJl9woaOascjh48MVrhDbJbh_6W_Dv3fGXziureu6N-Fy67T5EqMrTO0U7Lewe32LSmMukUBpdjacFOzN624Dzu8IPgkc_S56AaCY4Jewa6dNSroc_QPS6NaedVuGRWNK0c0Knh0zj3AnKqHnyEo-Kxm99PHk9R7Pt2JQcm9ect-gC3r2qvEIg1Z57p9AfH6ZcQPLln4T-ImSt_8hsb77FImIB7e2iTC4fpjrGsnSSie6ZNSDEQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/686d94e268094cd19fb723a143f36cc6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2sumRh-IsxXHr23jVVZ01vHFIBS3pdaJnVPhKaNt5DITV2diiInVIllwi4QagqBTITMlP4BbtMvqL4KG738zEiWtoUrE8q4_NBS2-EBbnbfA4rQD5idvSl7UJHy4i97Qi9pYVANO1-Wf56OdgJaOBHalIIlnpNuDv4LpwEai0Z2pSsED8Lew76ASGHsKVsvbGpXZGfFfkfSMtyNdZFjdXT5cT8fMpItarjFTUQYN8qMqX78blu0ix8wGKGp9gdkW5cZRk7jvKwKAVmSO7MAoZ9b1PAjMTabcFeJXLWB47m6Y0SMu_gKBQ2iwR2pvsfN6E9fV-LTqapSdeWB9KY8EWw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/0f2e82f40f0c4e55801f12dcd7358cdd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vXvPH8bSrf-mzU71qyIBodNsakMQF-Hnn35GD2TYOypzCgTklRuM93obwaCOhVpWLRZeGnB1nMq-AwRy_WCiDVQvGKgB9JY7aZPAEcQ4BCGWYi4b17Jf-trJdi4iSpkX4L7r9hs6zbJDXGWiXDnYFbfZt31V_QpKcbIhIzhgNkZHKrTeKGNoOI48qtmVLHh-VZgPqogK5TFUg0RF3xZy25Dj85MZAQewzwB-VdQu_3pU-Vqr6hUdLhGT04SDr69iqZwJDHwsJuAMMZ5DaF8KeqG_13t9kbd87KdIpqlFQVZgyj_fG_oe9hQt1_Qyyfx3RysLZz8dnsYXRtMllB1CNw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/fd2fcf62456e48ceb1252f9d37744e63","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tDu_5aLxMteI4ni1Lu9sZYYdwzPO7sKqbqK05SmC5UbA3WBaY6s9FFcNUxXmoZ6_HgegloytRH0lZEsPr24LayPQu8pWiDuPlXqQ24lGU7TOMyX45-bRhx-CM8v5SnQ3g-imoBgDPD84d7VtwCsjJJ3JUDr848bpjPv6rnoInAwKp06loP1e6nFcmLjunD3lza7BU5WFir14eRTr8eup4983wHWtQdksByG5m2y76yolyDrkrLmna4o4CUJXtrF5n2P3WSoFR4FqPsPsLBIjYHtfqsyv6P27vU5aEwjVwOq5HoNQsiqgu3riBl47UnaBZ6UfBW4316W1EfN8UAYVgQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -93,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/73966c5a98a44e60a02137fbe86f6b80","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"k-Abmh4c7e5Lky5wTThIiq4Tx8F5SNmfa0qWtVMshQjlUVsop1ZwTdL4uwRjLu6ulRBAE0hP4IkgdS8wG9a-cscpWcDnyUHJsbUBJlGXgLdjHC7V33cFlfX6EDvBugDBeBC38qYgWWYtKYuncPO8LTFDGzCjx3J5nCXIP89Qpe99cireFAST1TXinNSGzlDe7h38VNbs7cup3BhMMz1i254BdXfnlDF6Dz5hQrHo-fh-RX9tImacAfqNMIx2vCeBn-JN2Fh6lQRVNNDeEeXi8CxNbKa8cSHJmlZc_pAUZTQxbIzzz6kAGpXKawJbnxnq8slGx-7smI-b1mjRjAGdRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/d8f79e5ca9714676a180daca739a0d14","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pV1TsonpAp-IlbacQQgDDG-fXSbwI3infsG6K7zrm35kbq380WBX6h4ylBJG7dhyLF8592uk8Ilav_Q-ELARR_12zjrpvRYuZDs-LoecOxS0Q2RewK4CJhxUrtUSM0v-KUK3HgVUSv_Ryj3MItBZmdCN5j1NBbGu5dHjyOZGgHqJEOp5nhQ3WVYWGac5L8GvvDYry9tLAL6LZhJFqGtDyeo6cfNiUWBBU-qLO714BWhhL4gcpOiBIx6iZEOGR_Ljy48c_NHGhffhxnaDWke-orORg1sGF7s6TnUs6kQpcmM0wqwQALBttuIJ3Cn_SHXsERrktNd67ZCYD71QA7yakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/97e6bf573c0f45dfb26e28ce2a68bb08","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1et8hyXMgP59UQG8I66JTmCt2HvKgJ8buPUneL8ZjcREIATpPjehRaVxO-3w3VXsZB_PVhFGZVcVkameF3dB1yBEelN6h2GivFiB3iaFr-34JQ23jVLOxHmYmcm4dM6qWwGgQa6H2sjMAjB0y254pBSZhwbCHjatv9Eb9tdJAc2P_GrT1SJmKm8HV-EQnEsV9BTTg3ScXtEu55eIIlEY5kcQS54cLarb-_qaU0LzehZV5bjove3-0QWj3weS4fMAbVGyMPvjF-pnmpNQXC9bf6XujekE1LbFY7bugzHwjuzjvFkVoQ6XryukGFsOwxVKFcUlCiZCVWOYbe4AuzJe2Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/498735e6810c4c5288de95128f457c99","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ivl6v2jEFPA1mi6CAFeFzg0PyKK48Ws2TniUlpuhJA45U1n7WmfExeHace10yQOdPrBL5TNXS68kQfP5pnFGil2Fyaow2nA3tocuXpDFycr-_d1E6QyOakJBubNYsdYTEnA4agnIxXk_-50gvhd0rAkvjHQ6Mft_W6XrQFRQNeq9G-OpxhawteyHti7CWJ5aJTeO3ZZJv87PRjyCl6hW2eTCw8vUF63YppfUQthQhxrOTa4S8s3AbZnxx2z-imXMl0UKrJVfMic42ZmnYyfD6aG32qR4UvpuLf7mzw1OS1i5o96TKEwm9kHOxcLzO_DqKR0EoqJ_mzAmkRGcnWxPKw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -197,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/5c11cabd51574ff0b5bba5627cc8a459","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vUaIcLnSONZItdgEbJoKOveyE9tPsi-E5MpiC3E1srjshQsR35WyP_JCh3LUYp2zIsTzi_MoNlVPnwslh8gyxo4O4Ahjb0579Jtn854jlY1ugFgsvlZ9DzRcYNy37ttfVacrMsFCrSPykFBEH5JWCFjFIp3Js_z5esMnSHXkxUUHx-DRkpfkVZeCUWr-3rKXxdeNM20vThsr78MTc7FQLwluv7nSvfde1DRkgCSXYxqF25H2CN4oA0o2f6Ohdy9pXyG1PrDOUS39P4l5UjobI918ZVlU1eI0DJmhZT1sgKN2qs4KEnHlWvhftO5Zzi5Klbw1Qvirq84xWp-FViAkzw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/2d38fb708ff342b880f47d5effb8c1c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nIBs7t8mbqeD2L416R-gErP0FLDryHjGz6R5kJff3RKz94H1_nj1wVuQmaOQiKl7MRT1Es6-xZ1kKidgI2ZJ4C42i8fZ7tplcLbxUb7IjWPRFA8mvkw4CoHnGg_k4-zrlKLQCRo02HFCsNqqlG4OM_wcnI1kF-mBxCR3cfCWt2yEsYB7bKEaDM7X4R_toSEkk408_qD9ft2ZNkFKcRN213lVJneDkXbjrW4Tb2GuuYQcXjJ6qB0RNxdVYKFDLMW26eXncPT0RA5P2Vzehdu3ZrLZ5nrhM9dhan27LSrw9SBpq6tVdwi7pQNjc4A-GlcUnXEeeBMCsHGeZTNwo8pL1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/9ecaf929e95044f1b8eb35a462011d40","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tRdChPQgH3LReJ-vzWRTrDGHZgS9tJ2PS8nPo11jU6T7tRNU_eoysu1UM3O1dDubS6Arb_pf86emNsQP6COHrUl3dx8k1bOv2U_JYynFg68iufbP9L0uabf8_YGYGIKOEoin5kUQzp0CAW6ZmnYGW23TjeS9XYLV23NU9KHeyWLtPalLdFIlIu0PG9FzlxrJHpHzTQWOlsbGV8b7W2CuQa-lqjmyRhk2vh3ZTvw2mkSEAZacIhuARaiXrrEfvclff1AC5pvXUjY7ld5ZP2MWqgY6auRUY_y1y1fFcDI-gLT2gsVCllzU1bkIUEqRyL6mgxT5J3D9-FSCAZnkIh6kPQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/36d60c3c8ccc4012bd3bf210bbfced6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"whTwZlFRBHspYD4HZaysGb-xZWjIQ7SOY2tORFJpMAX1cvJMzp3XOcxPk57gMpIAntX8z3BjJLnYa6RA8-iZvqjil6NwxQYZ0zXTMO-FHxg30ED3MMjogSc32HaMv826K8R0F9FTn1bTfrw5jmkorAOB0CymPh0cSB_z1ywxMDv_qMJaQ2DWvOvuCqQaKZl20arA-90xgvMAxRCG5obOvH7ELX-6gDLIcQq3RDrf1kBzS2EWFXBsFC9ik1CcJDFS2C4bhiy-IkswTHENdvmh7hjUMpbm2QVIHIPwVnFMcLzvo-5xgDAWjTLuU_qH0FPyzDIcLMbet7zL09d00jU83w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -301,11 +354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/6749e30494c64c819476c7cba8cdaccc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"i_N2Zsjj0KXHcuIAs6mVBdBBD7OZIZmqgqtR5pcyCygxr2Q8bpgam8jd7EM_11O65zDgGJhLkQ6pczp07t2mRwvUuTUpap5oswLcwrkaMx0vlpzzacvPrf3ja2NZF_5OeimQr8pDG3l7YEijcK64Xdd_U1cDrjWcxi9rx_5C7uaYGiAiQHrSc8c2vkTHlOfQXMXkNPvgnpWtJiF8T6ocy1vlAw31uuVT5016pEmrsDHhNa_P-3qzj9IIFF8VmDVoXibWBAIRhxWUFL6XQ5mTWD6Ft5D30g9ZMDICyzTjhF-2xod-lZI0rgxIn-MHxhl5qlILVa8PVN3FfQ1Bpn1LMQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219239,"updated":1560219239,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/e4a37fd738b946abb32089d33bd04757","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"teMu3Y9A3DU2GAmK3mlC6uxqH2czgKWImqV176kx3gpwY2HxBhUH7KX08HuSN_lkbThwJBJV7yY2TYmJ49KndkDknr8qKRcAJ9wuJasR8QPkQqCTJ7Qb6nQCOWSJWK8kK95Dc9eYX3IiHfIUrhDup0GFMLMZ7WdsH7EMa-Nqt2XTGVZeTTvxOuqCUKh_prTtJGTFYLc8-7Z0ki1nw2FJ1w_W7r4d5uY7yAf4V8swbtvaaepUmnWupJ9pXtaVwpVTIZrPG_q5SywKZTqaxr7MOsow11mj4I3YUZTTEHNdfpgXkJq9VCOfN-KCwerjQqBD-Aaj8gJpAM70-O0gVZCs5Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -373,12 +426,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&maxresults=2 response: body: - string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/02c57a93dc494ba8a7ab0ae31674eb0c","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/0f2e82f40f0c4e55801f12dcd7358cdd","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TlVNeE1VTkJRa1ExTVRVM05FWkdNRUkxUWtKQk5UWXlOME5ET0VFME5Ua2hNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' + string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/2d38fb708ff342b880f47d5effb8c1c4","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/36d60c3c8ccc4012bd3bf210bbfced6d","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TkRrNE56TTFSVFk0TVRCRE5FTTFNamc0UkVVNU5URXlPRVkwTlRkRE9Ua2hNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' headers: cache-control: - no-cache @@ -387,7 +440,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:59 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -401,11 +454,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -421,12 +474,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TlVNeE1VTkJRa1ExTVRVM05FWkdNRUkxUWtKQk5UWXlOME5ET0VFME5Ua2hNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 + uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TkRrNE56TTFSVFk0TVRCRE5FTTFNamc0UkVVNU5URXlPRVkwTlRkRE9Ua2hNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 response: body: - string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/5c11cabd51574ff0b5bba5627cc8a459","attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/6749e30494c64c819476c7cba8cdaccc","attributes":{"enabled":true,"created":1560219239,"updated":1560219239,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TnpNNU5qWkROVUU1T0VFME5FVTJNRUV3TWpFek4wWkNSVGcyUmpaQ09EQWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' + string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/498735e6810c4c5288de95128f457c99","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/686d94e268094cd19fb723a143f36cc6","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2UkRoR056bEZOVU5CT1RjeE5EWTNOa0V4T0RCRVFVTkJOek01UVRCRU1UUWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' headers: cache-control: - no-cache @@ -435,7 +488,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:59 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -449,11 +502,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -469,12 +522,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2TnpNNU5qWkROVUU1T0VFME5FVTJNRUV3TWpFek4wWkNSVGcyUmpaQ09EQWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 + uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2UkRoR056bEZOVU5CT1RjeE5EWTNOa0V4T0RCRVFVTkJOek01UVRCRU1UUWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 response: body: - string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/73966c5a98a44e60a02137fbe86f6b80","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/97e6bf573c0f45dfb26e28ce2a68bb08","attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2T1VWRFFVWTVNamxGT1RVd05EUkdNVUk0UlVJek5VRTBOakl3TVRGRU5EQWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' + string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/d8f79e5ca9714676a180daca739a0d14","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/e4a37fd738b946abb32089d33bd04757","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Purgeable"}}],"nextLink":"https://vaultf5fa0e28.vault.azure.net:443/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2UmtReVJrTkdOakkwTlRaRk5EaERSVUl4TWpVeVJqbEVNemMzTkRSRk5qTWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2"}' headers: cache-control: - no-cache @@ -483,7 +536,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:59 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -497,11 +550,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -517,12 +570,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2T1VWRFFVWTVNamxGT1RVd05EUkdNVUk0UlVJek5VRTBOakl3TVRGRU5EQWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 + uri: https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/versions?api-version=7.0&$skiptoken=eyJOZXh0TWFya2VyIjoiMiExMjghTURBd01EVXlJV3RsZVM5VVJWTlVTMFZaUmpWR1FUQkZNamd2UmtReVJrTkdOakkwTlRaRk5EaERSVUl4TWpVeVJqbEVNemMzTkRSRk5qTWhNREF3TURJNElUazVPVGt0TVRJdE16RlVNak02TlRrNk5Ua3VPVGs1T1RrNU9Wb2giLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=2 response: body: - string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/9ecaf929e95044f1b8eb35a462011d40","attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"kid":"https://vaultf5fa0e28.vault.azure.net/keys/testKeyf5fa0e28/fd2fcf62456e48ceb1252f9d37744e63","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -531,7 +584,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:59 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -545,11 +598,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_purge.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_purge.yaml index d2b0c464fef0..946112d03ae0 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_purge.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_purge.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: !!python/unicode '{"kty": "RSA"}' + body: null headers: Accept: - application/json @@ -9,25 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '14' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault91110ab7.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/c8ae777e0cc3460ea8dfae7c25e3e0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r_2K-0Rn8k039UeFIdIwFbrAM4c7lUR1qIDE2NcxLizivsczZpafyzsd3kvwrdsNpyoaJeiXmVH-3ILWPcgehBIw5itHJ17kygZxQRe6zbbCg1amCSJLkoPgHBAlY4v8DntWkUPYOd2ccouI8NA-OedbKsXRgReIG_yqV6yxzmJqeFZ0GgGYMsAVxCqDnPmeAIB2q5eB3__0aRZBoQjrWEIH5oLNcf7cqUW5LQLU2dnBhehLuX-rO7RYnZkKAzsIYoF-KZjdIJ07gRL8Yejk0-yWs-G13-YwWiOwBTQM_cwP7fnjEHaanLJbAy_LcW61P-4MA2_ciem98Tv2nM_cFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode headers: cache-control: - no-cache content-length: - - '652' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Tue, 11 Jun 2019 02:13:55 GMT + - Tue, 09 Jul 2019 20:21:49 GMT expires: - '-1' pragma: @@ -36,21 +34,24 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: body: !!python/unicode '{"kty": "RSA"}' headers: @@ -65,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key1/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/d6f3905b325440808c1ff183f4865d32","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1zX2gqt4LvyaCuBYf90_KR2XBq51yhJHP-M0HiiwP3wUS2aIHMMcSQ8NEPLlrImEoro4xtuyy2UknMr_6OmWxfDnShLIeecqiTpgMojvnTg8rkdGkmq8PLRMPb_1r-SRTIW6A6Aioqmxc7QQX1uRrsZucCTtM0wcM0-QAPWDnf0Aq_oo112oaf05Fef8mm7CMlVh_GvCaSZHhHF9CD48r7zJG8I2tth1ZR23LsYtmS3WMPWDNuekM9WxsLKTz0fVRtQ4dkdLccvJTsrTgbc5zgm6nw_CxKPEV2AgsT4XJfYm48Hz8hqRhVH4sJ4HUO4f-1282NuPZ1oNGZGSBVEfCw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/2d73ed63217b46259d2e7ef0c1ce3b21","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"n2blTf8Guc6Nl3bJ2JCuUgDRGadMtmJIsvAQ_qBnbEQAaU3-Dmxi7krR8imJ2boIRvikFc30NVINZ0K2xowIlGUUCtZeYLz3UeDUzzvpxmXh7eDyrurQ3FjOS5rszyPXfNeB9F1vXRSmoxAarkEGazFp4gBk8TneuZSW8aqhUwAsp_gX7DGGCz3_aL1DLXWivwU4Gxau2IMphfb0RLUtt3XztWhXf3hCvCa46K3cwiJ9shkX5JxD9lEOsOJ9qjfZrvV3N_7p-eIp8yJAIKgMTJBTq3m7goC19b_BtHrpKD6TVJMflc4DKMGoh9l0Y2sgu63T9jMKluS32ArSP84ZSw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:55 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -93,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key2/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/0697507962ef47cf865424f048c2e144","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lRfvW5nheFGL_cGSq4Exs-Disic5v-WoRmKYm1FGHVuEWC2FPSJshfN6sEUsLej9BafHgRGQRPudGfmasJVy5IKtYCaBfZkPATROmTdB3O0kUOaja3DDoKK85i7KhhB3abvUoImbl79DivgY_59wqSmGyGJwkHYal2UQ2wmWPzN284UAS6OgELmNT1T4w87Rsoz5jfZuLqTTCYC2ljOOEPtZR9FOFu38FJMse_5H4XzLtSzdkmj-rYxhFHPN6cTkiC3h46FciRYlwU6SAG43mdGnUg0efIn9IIvsKoLxfDzCltpSB_taeHl0BXnM5XkM8t4a4eF6m5--Fskczm-niw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/baf6b81afc5042a4b72deafd75e390d6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mUTXMTvZGA5ZZtWU8FfiwF0bWTTgj0KaSa6p-ZGVaOH7gPWwVCdb4rTh8cRXwWsK7T6eUsCZss9irD75ydDQP8KJJCM_zL-Tv380u0rJNkP34TrYQ89seOm74cODVBrPnKVmW8UB9DsMwws9TwySrvFqqEd1I08sCnvg8w_dwnGHf_TFG6vYBPeNZcGkTjsm84ABGJJU6Udcc8WQArYh7TGitdKnKlbHbsuyRysYadauL9tMrQIRO-p-0-DkTSjlEv1HrgM8sCGZ1xw7a1YZMcL5HvZNy4UbSmUm4j-v4BLtcyzGcg9FfB9jZPwx8bfUffXOAgnmPJw0r0ngbukn7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -145,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key3/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/9a8262556d654c419e87af9b2c96a3bb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zWnGfePb-QO7_xT2tWKVp0RudkThFq76efNXXZQw8mP5TeWnIJ8vciz9RBh80xPDWQ_3etwYrw35fENzLBkooFUtP5GQ_PqysAQT7U3Nb270bMlgWuB1GYefcClCiUlwDgmjWLUTDgZrY_rD-CHRo9x-p1XYBi8SGsaPmXSoLfZ7qvIgMfW45p6P30tXHDoRMpWY7CrrjKaaEB9unc5_N2ZYjhctHhPr6MLVz2y7kZotESv7DB5j0GBmDKNcuVhbeHbi68Oj0O4b1KZRrgz36caeyxY16nZ5JVk0V1xRD0-GDx_c8DuVfngl1CeFVnhj9k5wbdSdWABXSiuliSFTaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/acfca8c0211a4d03a76f559d0edda51f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ld6e7TcJRd0V5MwPW-uq9bO5GSx7r90p7QpjIZA-0Meeh8SKFmiFB7np1ayWS548dwGssNOwSd0R6ScfFquyD3lJeXeGpeObus6g_WP8rmY8z5sfxwiFKPcimT9ACxRpBOqH5f_68q5G1981R-TSn376pvGaeahsO1PNJIZ-WkMTrvo3EcKoCShQV7AYX0gRYWJ_DCx0ics9m0FBaXJh-5MQ_Hn6oVVO2vBVPprZRbjFDq0WHbKjosuulepgG1QiEWaQvHVB_gwi_ab8a1ZJPlCE92iLJVOdZ10M4qX1JevAZSreifNHIZJvbnRXg6n5ATmhTVgNvAapRlWnej787w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -197,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key4/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/c075588319e743d38bc5b5aa47c3d294","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lU8mNS4f-1KMTY1tswMOXcewUqzP8N2_RHQCG3AdAFBBxwtNDwLW69elyEykyXitfOOnCvnqspFv20rrhkbtwcIQ3aGp2cAK_X6XbdVeIx_8TdjLbNT5hNTCNaac-u_K4aEAilD-kzoT2dx8KuWZta-hLOpLBuxtohJeo8q1siJWoaP-H166Z7u7tRdnXO2BcxTW3ta1McxxZZoMEJq2ZglLVM5gODUNCpR-kdG7MtFcInIsb9dulbPp569qYrHHWJgDPRhk3V7Xibr-18-25icDNlXDQyyG2jp8TQ-Fui_omTESjHAVNNJN19f5OdWY9HjMTnMnSUM7NxL_etxP3w","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/db6dccf225994f628eec7c7fb37637b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"olCV5G4mQjOOL66amUvn97_qamzgElLfGSfqdfdwqA9bYgvaQg4HRgX5QUs72XijsfpSNaM1ODn7-dHooystfB_dMDWXRoejsOlKIQBUlDAp6dDMVkJsOg5tlzo6Pk92rqrjBDOhnlfU86htCcUAjdUlMiZFcvrqQZQJ4X8_K74jk7s4IhVYoQUeAWCryI4Kibj1ziR2gwnTw1bkA1wY_yyGH7pgttv-LvJy95YDhOFCd_qdapZuOZZ0x0G3032hQFuu2b47y8_175VLW6uKqi3uysvHmbqZh0n72R-U7WASzjGfpB5a2hT9l-LVHMQI4L8cr_Hi8ZvNMYSWI4L3xQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -249,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key5/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/9dc8ca1358ee423fb01e710cae7f300a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"y_EVtDZzlyc0Kn3ETLJ4_mkDULZJQxIfl90Hi-GeTAUA9-maTWwo9pTv6_XbdjuDvJp1D9UVThrENe3Pr097FaFcbXGrItNYJr0CfIhmUGGSFB1xLQlsMttAITg1gzaieLQ35yuR6qD6qtiIPFR0IumbMpWXL1mRhuni3-qoxYJOzTLKqz6oxi3MDAA0HlOGYyt2tcpe2Dwn3uYZDza_ZoJYMBuHk6f-mbxGkl1vNUm8S5fO-yGCtEhgqmUC0tBPIiNO99Z4W78ITgEo1wbv69hXCHgbTw_n69d3kVKYH3eo23t21j0a12UM2vyT5ukwcAl9dcJGxLvFBDIwKaWC-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/3ee98c38847049808cd437f389c70855","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pjO8o2YsBmgTYVQoyzeHTPzmHWnFps0r7U6L7IcDcVOCmfjMuAjtgwMh67Vzc0dOJIz6i8Kr9u97TreWChfWE6Vzzk02egcctffxDmpzASn2FKb5MwhTMoo6C_2-cakV1BgtZ6gZHZGprDOfkJl4tUdKXGimdCITUfmgTgOnCYTSU3sTlsnR_DWtIemPV4to8p6YIz8rdxkVeYwpkYjkdIlgi2mcsjK97-KmtycbzziTaHeGVlaoJWQ2V6GzMow9VINKztN6JMc8KaHukwOBa_XJAfbB9tNGAH731MoY5JLh-FAvr7Z3-E4PPglCqceHCr8M4tz5SHpji3bEARHYkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -301,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault91110ab7.vault.azure.net/keys/key6/create?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/992afaa3cc6b42ff8aede665a3ecb6bd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"kP2SJVFhfFdpAVkfBjKNFizuUcHxeTj4Nty2HsdmwqtKciqoFiE1tPRh4FomCa9op9wa8E8zMOTkiEtl6mMxag4VENyWLLIUNO-fCEG1xd5Cif_XxDQ3wrOD3c6FWtTO-58I8pELjrFHJ14SbFOK84s1VQj2qZeG13aU3A3oapfiNs_kS8rP9VmnJUsS0YvVmHGxUqKeyOGVJDCganC_oZMsuHo0U5NLjCW4pgcj2z6g_FrfeOKAvZZ46zPF8nLHruxDq1PZqlUuWCJlWHMDO_rQCsd7XOB0EGczR8Lry8_lWsYPf7X9i-KLJMsMKc5x4dMeDn9EXC7v-Dv7LM0GmQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/b762540ae7c8440890aa9861f65c2e58","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"4fXXUsQgLx5FcFsIiPu0-o1Z9P1Ns6tqa4XByeN-Pax4aCDUZO3oq-H8QsyqXBpRMV99-MPHAPXfb3bMXIowqmPg5C0Jk6xq3lAnMHkAhX0D9d_uIYvj0rqM282q20iqK1JWsmxhzPnyeppBhfFfS4nXxRN0xFIKEVFMG4MG21b7Az6ddKRG9FS6PeOqi2150tA7CD6r21-S30zCJrMqqxm52ck4SLhZCPDEwUFLN1y4n3kUtyj7yq6n0B51KFaTr19zF605OmhAyHu5Qkue9KZmHPhVZ2W7eMGuDlsBsdXBlOKKOBm6J4n5AVRG14KbIUiQQBYANs4Yf_WRUsVIuQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -353,18 +354,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -373,23 +374,25 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '14' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key0?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault91110ab7.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/c8ae777e0cc3460ea8dfae7c25e3e0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r_2K-0Rn8k039UeFIdIwFbrAM4c7lUR1qIDE2NcxLizivsczZpafyzsd3kvwrdsNpyoaJeiXmVH-3ILWPcgehBIw5itHJ17kygZxQRe6zbbCg1amCSJLkoPgHBAlY4v8DntWkUPYOd2ccouI8NA-OedbKsXRgReIG_yqV6yxzmJqeFZ0GgGYMsAVxCqDnPmeAIB2q5eB3__0aRZBoQjrWEIH5oLNcf7cqUW5LQLU2dnBhehLuX-rO7RYnZkKAzsIYoF-KZjdIJ07gRL8Yejk0-yWs-G13-YwWiOwBTQM_cwP7fnjEHaanLJbAy_LcW61P-4MA2_ciem98Tv2nM_cFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/8bcaa8768cb64e7d996a9631a92c8607","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zcyhCjouu-esNMxJpKu8LBURTDAMur8fu684EIOW2buAsGNZL7T92123VeyGMl1jQzmIdyW1ifxjNzV4AagpCULC7qGgZ9N5DwJZsdW66YcYvG1pJF3xaRKw1GUcCA0IxNRPrqfgggNQeYHwSsCqqCnQWCNcYvqRUEURyPn089kXlC9jOm9473DGdQauf-x7pkmntYw7G5FakKbypKzH7BLNjhC6UrfsoQbbKE9M6teY37qwkbjsgbWX9v3CR4cphQSO8i7UbrFl5no93-L0vd0PvUP_aPKUAxrqkE1Zw9ccrDX6crkGq-hiPqsAGItPbm6_Qty-OesDuCabdwLtWw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '779' + - '652' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -403,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -425,12 +428,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key1?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key0?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/d6f3905b325440808c1ff183f4865d32","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1zX2gqt4LvyaCuBYf90_KR2XBq51yhJHP-M0HiiwP3wUS2aIHMMcSQ8NEPLlrImEoro4xtuyy2UknMr_6OmWxfDnShLIeecqiTpgMojvnTg8rkdGkmq8PLRMPb_1r-SRTIW6A6Aioqmxc7QQX1uRrsZucCTtM0wcM0-QAPWDnf0Aq_oo112oaf05Fef8mm7CMlVh_GvCaSZHhHF9CD48r7zJG8I2tth1ZR23LsYtmS3WMPWDNuekM9WxsLKTz0fVRtQ4dkdLccvJTsrTgbc5zgm6nw_CxKPEV2AgsT4XJfYm48Hz8hqRhVH4sJ4HUO4f-1282NuPZ1oNGZGSBVEfCw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/2d73ed63217b46259d2e7ef0c1ce3b21","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"n2blTf8Guc6Nl3bJ2JCuUgDRGadMtmJIsvAQ_qBnbEQAaU3-Dmxi7krR8imJ2boIRvikFc30NVINZ0K2xowIlGUUCtZeYLz3UeDUzzvpxmXh7eDyrurQ3FjOS5rszyPXfNeB9F1vXRSmoxAarkEGazFp4gBk8TneuZSW8aqhUwAsp_gX7DGGCz3_aL1DLXWivwU4Gxau2IMphfb0RLUtt3XztWhXf3hCvCa46K3cwiJ9shkX5JxD9lEOsOJ9qjfZrvV3N_7p-eIp8yJAIKgMTJBTq3m7goC19b_BtHrpKD6TVJMflc4DKMGoh9l0Y2sgu63T9jMKluS32ArSP84ZSw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -439,7 +442,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -453,11 +456,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -475,12 +478,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key2?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key1?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/0697507962ef47cf865424f048c2e144","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lRfvW5nheFGL_cGSq4Exs-Disic5v-WoRmKYm1FGHVuEWC2FPSJshfN6sEUsLej9BafHgRGQRPudGfmasJVy5IKtYCaBfZkPATROmTdB3O0kUOaja3DDoKK85i7KhhB3abvUoImbl79DivgY_59wqSmGyGJwkHYal2UQ2wmWPzN284UAS6OgELmNT1T4w87Rsoz5jfZuLqTTCYC2ljOOEPtZR9FOFu38FJMse_5H4XzLtSzdkmj-rYxhFHPN6cTkiC3h46FciRYlwU6SAG43mdGnUg0efIn9IIvsKoLxfDzCltpSB_taeHl0BXnM5XkM8t4a4eF6m5--Fskczm-niw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/baf6b81afc5042a4b72deafd75e390d6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mUTXMTvZGA5ZZtWU8FfiwF0bWTTgj0KaSa6p-ZGVaOH7gPWwVCdb4rTh8cRXwWsK7T6eUsCZss9irD75ydDQP8KJJCM_zL-Tv380u0rJNkP34TrYQ89seOm74cODVBrPnKVmW8UB9DsMwws9TwySrvFqqEd1I08sCnvg8w_dwnGHf_TFG6vYBPeNZcGkTjsm84ABGJJU6Udcc8WQArYh7TGitdKnKlbHbsuyRysYadauL9tMrQIRO-p-0-DkTSjlEv1HrgM8sCGZ1xw7a1YZMcL5HvZNy4UbSmUm4j-v4BLtcyzGcg9FfB9jZPwx8bfUffXOAgnmPJw0r0ngbukn7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -489,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -503,11 +506,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -525,12 +528,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key3?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key2?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/9a8262556d654c419e87af9b2c96a3bb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zWnGfePb-QO7_xT2tWKVp0RudkThFq76efNXXZQw8mP5TeWnIJ8vciz9RBh80xPDWQ_3etwYrw35fENzLBkooFUtP5GQ_PqysAQT7U3Nb270bMlgWuB1GYefcClCiUlwDgmjWLUTDgZrY_rD-CHRo9x-p1XYBi8SGsaPmXSoLfZ7qvIgMfW45p6P30tXHDoRMpWY7CrrjKaaEB9unc5_N2ZYjhctHhPr6MLVz2y7kZotESv7DB5j0GBmDKNcuVhbeHbi68Oj0O4b1KZRrgz36caeyxY16nZ5JVk0V1xRD0-GDx_c8DuVfngl1CeFVnhj9k5wbdSdWABXSiuliSFTaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/acfca8c0211a4d03a76f559d0edda51f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ld6e7TcJRd0V5MwPW-uq9bO5GSx7r90p7QpjIZA-0Meeh8SKFmiFB7np1ayWS548dwGssNOwSd0R6ScfFquyD3lJeXeGpeObus6g_WP8rmY8z5sfxwiFKPcimT9ACxRpBOqH5f_68q5G1981R-TSn376pvGaeahsO1PNJIZ-WkMTrvo3EcKoCShQV7AYX0gRYWJ_DCx0ics9m0FBaXJh-5MQ_Hn6oVVO2vBVPprZRbjFDq0WHbKjosuulepgG1QiEWaQvHVB_gwi_ab8a1ZJPlCE92iLJVOdZ10M4qX1JevAZSreifNHIZJvbnRXg6n5ATmhTVgNvAapRlWnej787w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -539,7 +542,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -553,11 +556,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -575,12 +578,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key4?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key3?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/c075588319e743d38bc5b5aa47c3d294","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lU8mNS4f-1KMTY1tswMOXcewUqzP8N2_RHQCG3AdAFBBxwtNDwLW69elyEykyXitfOOnCvnqspFv20rrhkbtwcIQ3aGp2cAK_X6XbdVeIx_8TdjLbNT5hNTCNaac-u_K4aEAilD-kzoT2dx8KuWZta-hLOpLBuxtohJeo8q1siJWoaP-H166Z7u7tRdnXO2BcxTW3ta1McxxZZoMEJq2ZglLVM5gODUNCpR-kdG7MtFcInIsb9dulbPp569qYrHHWJgDPRhk3V7Xibr-18-25icDNlXDQyyG2jp8TQ-Fui_omTESjHAVNNJN19f5OdWY9HjMTnMnSUM7NxL_etxP3w","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/db6dccf225994f628eec7c7fb37637b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"olCV5G4mQjOOL66amUvn97_qamzgElLfGSfqdfdwqA9bYgvaQg4HRgX5QUs72XijsfpSNaM1ODn7-dHooystfB_dMDWXRoejsOlKIQBUlDAp6dDMVkJsOg5tlzo6Pk92rqrjBDOhnlfU86htCcUAjdUlMiZFcvrqQZQJ4X8_K74jk7s4IhVYoQUeAWCryI4Kibj1ziR2gwnTw1bkA1wY_yyGH7pgttv-LvJy95YDhOFCd_qdapZuOZZ0x0G3032hQFuu2b47y8_175VLW6uKqi3uysvHmbqZh0n72R-U7WASzjGfpB5a2hT9l-LVHMQI4L8cr_Hi8ZvNMYSWI4L3xQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -589,7 +592,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -603,11 +606,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -625,12 +628,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key5?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key4?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/9dc8ca1358ee423fb01e710cae7f300a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"y_EVtDZzlyc0Kn3ETLJ4_mkDULZJQxIfl90Hi-GeTAUA9-maTWwo9pTv6_XbdjuDvJp1D9UVThrENe3Pr097FaFcbXGrItNYJr0CfIhmUGGSFB1xLQlsMttAITg1gzaieLQ35yuR6qD6qtiIPFR0IumbMpWXL1mRhuni3-qoxYJOzTLKqz6oxi3MDAA0HlOGYyt2tcpe2Dwn3uYZDza_ZoJYMBuHk6f-mbxGkl1vNUm8S5fO-yGCtEhgqmUC0tBPIiNO99Z4W78ITgEo1wbv69hXCHgbTw_n69d3kVKYH3eo23t21j0a12UM2vyT5ukwcAl9dcJGxLvFBDIwKaWC-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/3ee98c38847049808cd437f389c70855","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pjO8o2YsBmgTYVQoyzeHTPzmHWnFps0r7U6L7IcDcVOCmfjMuAjtgwMh67Vzc0dOJIz6i8Kr9u97TreWChfWE6Vzzk02egcctffxDmpzASn2FKb5MwhTMoo6C_2-cakV1BgtZ6gZHZGprDOfkJl4tUdKXGimdCITUfmgTgOnCYTSU3sTlsnR_DWtIemPV4to8p6YIz8rdxkVeYwpkYjkdIlgi2mcsjK97-KmtycbzziTaHeGVlaoJWQ2V6GzMow9VINKztN6JMc8KaHukwOBa_XJAfbB9tNGAH731MoY5JLh-FAvr7Z3-E4PPglCqceHCr8M4tz5SHpji3bEARHYkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -639,7 +642,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -653,11 +656,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -675,12 +678,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault91110ab7.vault.azure.net/keys/key6?api-version=7.0 + uri: https://vault91110ab7.vault.azure.net/keys/key5?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/992afaa3cc6b42ff8aede665a3ecb6bd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"kP2SJVFhfFdpAVkfBjKNFizuUcHxeTj4Nty2HsdmwqtKciqoFiE1tPRh4FomCa9op9wa8E8zMOTkiEtl6mMxag4VENyWLLIUNO-fCEG1xd5Cif_XxDQ3wrOD3c6FWtTO-58I8pELjrFHJ14SbFOK84s1VQj2qZeG13aU3A3oapfiNs_kS8rP9VmnJUsS0YvVmHGxUqKeyOGVJDCganC_oZMsuHo0U5NLjCW4pgcj2z6g_FrfeOKAvZZ46zPF8nLHruxDq1PZqlUuWCJlWHMDO_rQCsd7XOB0EGczR8Lry8_lWsYPf7X9i-KLJMsMKc5x4dMeDn9EXC7v-Dv7LM0GmQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/b762540ae7c8440890aa9861f65c2e58","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"4fXXUsQgLx5FcFsIiPu0-o1Z9P1Ns6tqa4XByeN-Pax4aCDUZO3oq-H8QsyqXBpRMV99-MPHAPXfb3bMXIowqmPg5C0Jk6xq3lAnMHkAhX0D9d_uIYvj0rqM282q20iqK1JWsmxhzPnyeppBhfFfS4nXxRN0xFIKEVFMG4MG21b7Az6ddKRG9FS6PeOqi2150tA7CD6r21-S30zCJrMqqxm52ck4SLhZCPDEwUFLN1y4n3kUtyj7yq6n0B51KFaTr19zF605OmhAyHu5Qkue9KZmHPhVZ2W7eMGuDlsBsdXBlOKKOBm6J4n5AVRG14KbIUiQQBYANs4Yf_WRUsVIuQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -689,7 +692,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:58 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -703,11 +706,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -722,72 +725,24 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 - response: - body: - string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted - Key not found: key0"}}' - headers: - cache-control: - - no-cache - content-length: - - '72' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 11 Jun 2019 02:13:58 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault91110ab7.vault.azure.net/keys/key6?api-version=7.0 response: body: - string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted - Key not found: key0"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/8bcaa8768cb64e7d996a9631a92c8607","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zcyhCjouu-esNMxJpKu8LBURTDAMur8fu684EIOW2buAsGNZL7T92123VeyGMl1jQzmIdyW1ifxjNzV4AagpCULC7qGgZ9N5DwJZsdW66YcYvG1pJF3xaRKw1GUcCA0IxNRPrqfgggNQeYHwSsCqqCnQWCNcYvqRUEURyPn089kXlC9jOm9473DGdQauf-x7pkmntYw7G5FakKbypKzH7BLNjhC6UrfsoQbbKE9M6teY37qwkbjsgbWX9v3CR4cphQSO8i7UbrFl5no93-L0vd0PvUP_aPKUAxrqkE1Zw9ccrDX6crkGq-hiPqsAGItPbm6_Qty-OesDuCabdwLtWw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:02 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -801,16 +756,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -821,7 +776,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -836,7 +791,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:05 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -850,11 +805,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -870,7 +825,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -885,7 +840,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:08 GMT + - Tue, 09 Jul 2019 20:21:56 GMT expires: - '-1' pragma: @@ -899,11 +854,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -919,7 +874,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -934,7 +889,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:11 GMT + - Tue, 09 Jul 2019 20:21:59 GMT expires: - '-1' pragma: @@ -948,11 +903,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -968,7 +923,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -983,7 +938,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:15 GMT + - Tue, 09 Jul 2019 20:22:02 GMT expires: - '-1' pragma: @@ -997,11 +952,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1017,12 +972,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/c8ae777e0cc3460ea8dfae7c25e3e0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r_2K-0Rn8k039UeFIdIwFbrAM4c7lUR1qIDE2NcxLizivsczZpafyzsd3kvwrdsNpyoaJeiXmVH-3ILWPcgehBIw5itHJ17kygZxQRe6zbbCg1amCSJLkoPgHBAlY4v8DntWkUPYOd2ccouI8NA-OedbKsXRgReIG_yqV6yxzmJqeFZ0GgGYMsAVxCqDnPmeAIB2q5eB3__0aRZBoQjrWEIH5oLNcf7cqUW5LQLU2dnBhehLuX-rO7RYnZkKAzsIYoF-KZjdIJ07gRL8Yejk0-yWs-G13-YwWiOwBTQM_cwP7fnjEHaanLJbAy_LcW61P-4MA2_ciem98Tv2nM_cFw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key0/2d73ed63217b46259d2e7ef0c1ce3b21","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"n2blTf8Guc6Nl3bJ2JCuUgDRGadMtmJIsvAQ_qBnbEQAaU3-Dmxi7krR8imJ2boIRvikFc30NVINZ0K2xowIlGUUCtZeYLz3UeDUzzvpxmXh7eDyrurQ3FjOS5rszyPXfNeB9F1vXRSmoxAarkEGazFp4gBk8TneuZSW8aqhUwAsp_gX7DGGCz3_aL1DLXWivwU4Gxau2IMphfb0RLUtt3XztWhXf3hCvCa46K3cwiJ9shkX5JxD9lEOsOJ9qjfZrvV3N_7p-eIp8yJAIKgMTJBTq3m7goC19b_BtHrpKD6TVJMflc4DKMGoh9l0Y2sgu63T9jMKluS32ArSP84ZSw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1031,7 +986,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:18 GMT + - Tue, 09 Jul 2019 20:22:05 GMT expires: - '-1' pragma: @@ -1045,11 +1000,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1065,12 +1020,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/d6f3905b325440808c1ff183f4865d32","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1zX2gqt4LvyaCuBYf90_KR2XBq51yhJHP-M0HiiwP3wUS2aIHMMcSQ8NEPLlrImEoro4xtuyy2UknMr_6OmWxfDnShLIeecqiTpgMojvnTg8rkdGkmq8PLRMPb_1r-SRTIW6A6Aioqmxc7QQX1uRrsZucCTtM0wcM0-QAPWDnf0Aq_oo112oaf05Fef8mm7CMlVh_GvCaSZHhHF9CD48r7zJG8I2tth1ZR23LsYtmS3WMPWDNuekM9WxsLKTz0fVRtQ4dkdLccvJTsrTgbc5zgm6nw_CxKPEV2AgsT4XJfYm48Hz8hqRhVH4sJ4HUO4f-1282NuPZ1oNGZGSBVEfCw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key1/baf6b81afc5042a4b72deafd75e390d6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mUTXMTvZGA5ZZtWU8FfiwF0bWTTgj0KaSa6p-ZGVaOH7gPWwVCdb4rTh8cRXwWsK7T6eUsCZss9irD75ydDQP8KJJCM_zL-Tv380u0rJNkP34TrYQ89seOm74cODVBrPnKVmW8UB9DsMwws9TwySrvFqqEd1I08sCnvg8w_dwnGHf_TFG6vYBPeNZcGkTjsm84ABGJJU6Udcc8WQArYh7TGitdKnKlbHbsuyRysYadauL9tMrQIRO-p-0-DkTSjlEv1HrgM8sCGZ1xw7a1YZMcL5HvZNy4UbSmUm4j-v4BLtcyzGcg9FfB9jZPwx8bfUffXOAgnmPJw0r0ngbukn7Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1079,7 +1034,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:18 GMT + - Tue, 09 Jul 2019 20:22:05 GMT expires: - '-1' pragma: @@ -1093,11 +1048,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1113,12 +1068,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/0697507962ef47cf865424f048c2e144","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lRfvW5nheFGL_cGSq4Exs-Disic5v-WoRmKYm1FGHVuEWC2FPSJshfN6sEUsLej9BafHgRGQRPudGfmasJVy5IKtYCaBfZkPATROmTdB3O0kUOaja3DDoKK85i7KhhB3abvUoImbl79DivgY_59wqSmGyGJwkHYal2UQ2wmWPzN284UAS6OgELmNT1T4w87Rsoz5jfZuLqTTCYC2ljOOEPtZR9FOFu38FJMse_5H4XzLtSzdkmj-rYxhFHPN6cTkiC3h46FciRYlwU6SAG43mdGnUg0efIn9IIvsKoLxfDzCltpSB_taeHl0BXnM5XkM8t4a4eF6m5--Fskczm-niw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key2/acfca8c0211a4d03a76f559d0edda51f","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ld6e7TcJRd0V5MwPW-uq9bO5GSx7r90p7QpjIZA-0Meeh8SKFmiFB7np1ayWS548dwGssNOwSd0R6ScfFquyD3lJeXeGpeObus6g_WP8rmY8z5sfxwiFKPcimT9ACxRpBOqH5f_68q5G1981R-TSn376pvGaeahsO1PNJIZ-WkMTrvo3EcKoCShQV7AYX0gRYWJ_DCx0ics9m0FBaXJh-5MQ_Hn6oVVO2vBVPprZRbjFDq0WHbKjosuulepgG1QiEWaQvHVB_gwi_ab8a1ZJPlCE92iLJVOdZ10M4qX1JevAZSreifNHIZJvbnRXg6n5ATmhTVgNvAapRlWnej787w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1127,7 +1082,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:05 GMT expires: - '-1' pragma: @@ -1141,11 +1096,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1161,12 +1116,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/9a8262556d654c419e87af9b2c96a3bb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zWnGfePb-QO7_xT2tWKVp0RudkThFq76efNXXZQw8mP5TeWnIJ8vciz9RBh80xPDWQ_3etwYrw35fENzLBkooFUtP5GQ_PqysAQT7U3Nb270bMlgWuB1GYefcClCiUlwDgmjWLUTDgZrY_rD-CHRo9x-p1XYBi8SGsaPmXSoLfZ7qvIgMfW45p6P30tXHDoRMpWY7CrrjKaaEB9unc5_N2ZYjhctHhPr6MLVz2y7kZotESv7DB5j0GBmDKNcuVhbeHbi68Oj0O4b1KZRrgz36caeyxY16nZ5JVk0V1xRD0-GDx_c8DuVfngl1CeFVnhj9k5wbdSdWABXSiuliSFTaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key3/db6dccf225994f628eec7c7fb37637b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"olCV5G4mQjOOL66amUvn97_qamzgElLfGSfqdfdwqA9bYgvaQg4HRgX5QUs72XijsfpSNaM1ODn7-dHooystfB_dMDWXRoejsOlKIQBUlDAp6dDMVkJsOg5tlzo6Pk92rqrjBDOhnlfU86htCcUAjdUlMiZFcvrqQZQJ4X8_K74jk7s4IhVYoQUeAWCryI4Kibj1ziR2gwnTw1bkA1wY_yyGH7pgttv-LvJy95YDhOFCd_qdapZuOZZ0x0G3032hQFuu2b47y8_175VLW6uKqi3uysvHmbqZh0n72R-U7WASzjGfpB5a2hT9l-LVHMQI4L8cr_Hi8ZvNMYSWI4L3xQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1175,7 +1130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:05 GMT expires: - '-1' pragma: @@ -1189,11 +1144,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1209,12 +1164,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/c075588319e743d38bc5b5aa47c3d294","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lU8mNS4f-1KMTY1tswMOXcewUqzP8N2_RHQCG3AdAFBBxwtNDwLW69elyEykyXitfOOnCvnqspFv20rrhkbtwcIQ3aGp2cAK_X6XbdVeIx_8TdjLbNT5hNTCNaac-u_K4aEAilD-kzoT2dx8KuWZta-hLOpLBuxtohJeo8q1siJWoaP-H166Z7u7tRdnXO2BcxTW3ta1McxxZZoMEJq2ZglLVM5gODUNCpR-kdG7MtFcInIsb9dulbPp569qYrHHWJgDPRhk3V7Xibr-18-25icDNlXDQyyG2jp8TQ-Fui_omTESjHAVNNJN19f5OdWY9HjMTnMnSUM7NxL_etxP3w","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key4/3ee98c38847049808cd437f389c70855","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pjO8o2YsBmgTYVQoyzeHTPzmHWnFps0r7U6L7IcDcVOCmfjMuAjtgwMh67Vzc0dOJIz6i8Kr9u97TreWChfWE6Vzzk02egcctffxDmpzASn2FKb5MwhTMoo6C_2-cakV1BgtZ6gZHZGprDOfkJl4tUdKXGimdCITUfmgTgOnCYTSU3sTlsnR_DWtIemPV4to8p6YIz8rdxkVeYwpkYjkdIlgi2mcsjK97-KmtycbzziTaHeGVlaoJWQ2V6GzMow9VINKztN6JMc8KaHukwOBa_XJAfbB9tNGAH731MoY5JLh-FAvr7Z3-E4PPglCqceHCr8M4tz5SHpji3bEARHYkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1223,7 +1178,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:05 GMT expires: - '-1' pragma: @@ -1237,11 +1192,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1257,12 +1212,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/9dc8ca1358ee423fb01e710cae7f300a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"y_EVtDZzlyc0Kn3ETLJ4_mkDULZJQxIfl90Hi-GeTAUA9-maTWwo9pTv6_XbdjuDvJp1D9UVThrENe3Pr097FaFcbXGrItNYJr0CfIhmUGGSFB1xLQlsMttAITg1gzaieLQ35yuR6qD6qtiIPFR0IumbMpWXL1mRhuni3-qoxYJOzTLKqz6oxi3MDAA0HlOGYyt2tcpe2Dwn3uYZDza_ZoJYMBuHk6f-mbxGkl1vNUm8S5fO-yGCtEhgqmUC0tBPIiNO99Z4W78ITgEo1wbv69hXCHgbTw_n69d3kVKYH3eo23t21j0a12UM2vyT5ukwcAl9dcJGxLvFBDIwKaWC-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key5/b762540ae7c8440890aa9861f65c2e58","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"4fXXUsQgLx5FcFsIiPu0-o1Z9P1Ns6tqa4XByeN-Pax4aCDUZO3oq-H8QsyqXBpRMV99-MPHAPXfb3bMXIowqmPg5C0Jk6xq3lAnMHkAhX0D9d_uIYvj0rqM282q20iqK1JWsmxhzPnyeppBhfFfS4nXxRN0xFIKEVFMG4MG21b7Az6ddKRG9FS6PeOqi2150tA7CD6r21-S30zCJrMqqxm52ck4SLhZCPDEwUFLN1y4n3kUtyj7yq6n0B51KFaTr19zF605OmhAyHu5Qkue9KZmHPhVZ2W7eMGuDlsBsdXBlOKKOBm6J4n5AVRG14KbIUiQQBYANs4Yf_WRUsVIuQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1271,7 +1226,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1285,11 +1240,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1305,12 +1260,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key6?api-version=7.0 response: body: - string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/992afaa3cc6b42ff8aede665a3ecb6bd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"kP2SJVFhfFdpAVkfBjKNFizuUcHxeTj4Nty2HsdmwqtKciqoFiE1tPRh4FomCa9op9wa8E8zMOTkiEtl6mMxag4VENyWLLIUNO-fCEG1xd5Cif_XxDQ3wrOD3c6FWtTO-58I8pELjrFHJ14SbFOK84s1VQj2qZeG13aU3A3oapfiNs_kS8rP9VmnJUsS0YvVmHGxUqKeyOGVJDCganC_oZMsuHo0U5NLjCW4pgcj2z6g_FrfeOKAvZZ46zPF8nLHruxDq1PZqlUuWCJlWHMDO_rQCsd7XOB0EGczR8Lry8_lWsYPf7X9i-KLJMsMKc5x4dMeDn9EXC7v-Dv7LM0GmQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vault91110ab7.vault.azure.net/keys/key6/8bcaa8768cb64e7d996a9631a92c8607","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zcyhCjouu-esNMxJpKu8LBURTDAMur8fu684EIOW2buAsGNZL7T92123VeyGMl1jQzmIdyW1ifxjNzV4AagpCULC7qGgZ9N5DwJZsdW66YcYvG1pJF3xaRKw1GUcCA0IxNRPrqfgggNQeYHwSsCqqCnQWCNcYvqRUEURyPn089kXlC9jOm9473DGdQauf-x7pkmntYw7G5FakKbypKzH7BLNjhC6UrfsoQbbKE9M6teY37qwkbjsgbWX9v3CR4cphQSO8i7UbrFl5no93-L0vd0PvUP_aPKUAxrqkE1Zw9ccrDX6crkGq-hiPqsAGItPbm6_Qty-OesDuCabdwLtWw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1319,7 +1274,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1333,11 +1288,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1353,12 +1308,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: !!python/unicode '{"value":[{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"kid":"https://vault91110ab7.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"kid":"https://vault91110ab7.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"kid":"https://vault91110ab7.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"kid":"https://vault91110ab7.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1560219238,"scheduledPurgeDate":1567995238,"kid":"https://vault91110ab7.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"kid":"https://vault91110ab7.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1560219239,"scheduledPurgeDate":1567995239,"kid":"https://vault91110ab7.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key0","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault91110ab7.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key1","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault91110ab7.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key2","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vault91110ab7.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key3","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault91110ab7.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key4","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault91110ab7.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault91110ab7.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vault91110ab7.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vault91110ab7.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -1367,7 +1322,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:19 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1381,11 +1336,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1403,7 +1358,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -1413,7 +1368,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:20 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1427,11 +1382,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1449,7 +1404,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key1?api-version=7.0 response: @@ -1459,7 +1414,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:20 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1473,11 +1428,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1495,7 +1450,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key2?api-version=7.0 response: @@ -1505,7 +1460,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:20 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1519,11 +1474,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1541,7 +1496,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key3?api-version=7.0 response: @@ -1551,7 +1506,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1565,11 +1520,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1587,7 +1542,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key4?api-version=7.0 response: @@ -1597,7 +1552,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1611,11 +1566,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1633,7 +1588,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key5?api-version=7.0 response: @@ -1643,7 +1598,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1657,11 +1612,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1679,7 +1634,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault91110ab7.vault.azure.net/deletedkeys/key6?api-version=7.0 response: @@ -1689,7 +1644,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1703,11 +1658,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1723,7 +1678,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -1738,7 +1693,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1752,11 +1707,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1772,7 +1727,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key1?api-version=7.0 response: @@ -1787,7 +1742,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:21 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1801,11 +1756,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1821,7 +1776,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key2?api-version=7.0 response: @@ -1836,7 +1791,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:22 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1850,11 +1805,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1870,7 +1825,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key3?api-version=7.0 response: @@ -1885,7 +1840,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:22 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -1899,11 +1854,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1919,7 +1874,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key4?api-version=7.0 response: @@ -1934,7 +1889,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:22 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -1948,11 +1903,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1968,7 +1923,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key5?api-version=7.0 response: @@ -1983,7 +1938,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:22 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -1997,11 +1952,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2017,7 +1972,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys/key6?api-version=7.0 response: @@ -2032,7 +1987,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:22 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -2046,11 +2001,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2066,7 +2021,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/2.7.15 (Windows-10-10.0.18362) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault91110ab7.vault.azure.net/deletedkeys?api-version=7.0 response: @@ -2080,7 +2035,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:23 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -2094,11 +2049,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_recover.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_recover.yaml index be1cfcf872fa..d683f9b867af 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_recover.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_key_client.test_recover.yaml @@ -1,6 +1,59 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaulta7710b8a.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:21:49 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/c7a911d13a1245cda1b2424fef3c1b9d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1tnHqcAuo1Kvcz89mrSxqTU7H4J7tpr_aCejPHAltr2KFWKMkqpZ2ixaSyfZTYZX-Hi1hBoxBT-Xl_1T9_nlCM_aV_rua4tlz4XouNQI5GoFW85Alavj03yJ86UmEKnvHAe4QFvW-WCrJSWhUe-St7fWqzAEO5666YRmBaJRu1c9dJGNSoagbMzPfD4Ni8FgJr3Y1Ql9Cxx6EwxvcRV4r6kC8alzIJNzYwRrnFyJeBPDIKnuo1qVnMbXC2fLWxiA8aFICojisWQzpAVnsNDvVs26_6CHprqencRxAArsTNZtjpF45v98g-pxtOXXHpHEIR27WLciohhQ9LwZqVVsRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/fb5a7e7a96b74d03b9aeea58d8f85726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wtMCrNE3c3WAQHeiWDSo6O6S3Ru__rbgGOwwcL6fxhhLt0vhbUsRP93rmZI1adjd3PE3o-8priHBnPsC__cJriDnvqfKZVA_iWJhQ5U6cXiINu1R2rlGh7gD3YsRrhzpRTRFdxhaQ9pa-0G3mGe6jMBOAujRW3Ch9HaB6yAE5fYL65WWrvpmshTIUeJZuTMGVvyCNgdQmSV472oF_E_k3CpLBhUj5hbUlNr59JhLonTm0uuq0FMm5jaE-caRcOfrGYFVYTVWXLygj25JF2lbM79Qu87n75r1BoLbk5Jcot8N14GR5KYKyTRGVdc_sMpLWuljTMUzmy0tylCZtKJPiQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:57 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -41,18 +94,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/5994595017e34fefb0faacc6ebba0ad0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pvz2unI2zFwKTCfAlEMIvYjpDY-IdpRDVZgqIQgtMufQf9xd-n4P76Fv3i0-oV7ZJu9F85NZlBvL9McVzT1UiuNM_nR80mYWV8MxsCyAOMOURtlk7E1xBOBrelq-WeRRx1pvRq1NmQHc8X5Y0-DLLS_0QMYmL2yWo2csIeCvy9llzF3_sv0jG1UpkA7FeOGw7vBb0oFk_ZSXe07b5r-QL0hPU7ft2s6QrB7Mz012qZvEm9TFSVvI3Ekmx2m16Pcev3QX4hifKKKZ8ug8b927gIYcN1FkqVRuClXAkYAa363KNYlLnQIdJFzokni_3-jrN9dlFgWj8vY4SZcVCPPdVw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/2234742e6f39413f81bed92ad0c77b61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sniM0631T66BBT5MJLD0RqPUeqR-KbZ1CeCrySxc0AYtd5DeRiUmnvAAHjjovafGMYKOEYvt-CdcL_HzfzoCoQFRcUCuGWaBbhEGPkEbZSCvT9rpLQGAqI-WxsqaifgjvQtyXe_LeZfr0_rqQT8r1sPPWncfKd9JqvgNSdYTPp0hxbKMS-hVPbdor6CmLolNuIAVxWgmFntxAJXxnvdh64iGKKytewwWOkQFw7Q_5hLrD-BqwTu-j9wVMK48URXcl4K6bbQcBk7az8KL15we8hNz_VYYbO32N36BlTNL85XRLxM4vadEaY58Qcg4iZOAlFMTH5LkAxcYes6L5bl4nw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:58 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -93,18 +146,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/2f6cc1c870154e858fd9da6ce6bee500","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"msh2ewKKNPjus6qtMnXJdouK4HYpLUcZvk6I3K53exIz8mKgwURfyGHb_1_SofwdThi68dACpQVh_8t6NgPYQwIK3RDviDlsEM9-1FohYx-OuLf60ciZXImMNHIqhGggdpzbpbvTtVJx54DrYLBqh0Gpvql3I3W6YiKOBDxOXt1MG10IkTFk4G2y_I0NoKgIYwxTkQTds2pVvniDMWcSbbuB09BZOz0ST62rhO2TUa2mLe7_ZWj09OayR23R5vumGobbLMLSYRTWXhn_WKh4Sg6XzgwzpTVSpIfaD27ppoQJpGy6qRETANlTKIurGqMGq880AXes5MIIK4fHLPzEaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/600f30806c034e28bc148068f71991b2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pqZlmGmsTjk6--txP0bgy394cVPDEKiczm5GT0tPlwINGZeRRycLiHUmdvEyEKHRCDlYQO5lMIKh4_1q0AI_hKAt8CbClx5mDI5mjW4Bd6fiGn1GVPxhOtan8pbMQ9ADdliZvha0qA6vhDfjG-OdR2vKdNHvb9P841lNymq_kDrqyt1plha3A7WoHUkZW3_zWoxCWfFHz4A3vFRIsYNRpXbJeTKTEViXiQjCkLZTHntjqJfPIXdicooLER-2c4TpLNAtHsqmBWAm83BJnJ-Csyb1oGP2jpGW7X9wxa6MYiygAl4Y26e50O3fIOHxBEGcOkZl_d_piuy_HbwQYrS4Hw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:58 GMT + - Tue, 09 Jul 2019 20:21:50 GMT expires: - '-1' pragma: @@ -145,18 +198,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/ddacf2949c184f3781e184b3b7829ec6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ss8mYFywgnYUqIb9rE_-FVetI2TyNi1Ht0lmaPu0Y0aXAkMPFywrhoDdeRZdyd4H7SwG9CY7oIyc2Ch4KqZdCBJY5d3CUgQgcNg7P98tG8mmR8TMwsnh2WBChoXRny2u2ySPAPDwGBPQsu-00tRLe8e_ITMskdY-x-c7dKUjK97oJJb9Kh5atJL88EjpRBYh__V9qTqoXUKy83I2sg0aiLmSnk6L3YMJqAdgMnHMAYoZfzxSy4_gjZFC5a7CrvClxYKdWYzlk40JF3_0BHcrEvaDn6gY6IQfYF-tNKPGKHqw3MreTVNQNTPhpSnuhszFfu9giEub6HM47lx-_KaXoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/d8dbc4e087ef4d68bda02e39f4508841","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v5TQVWQ9SES-bYzE6C6G97PTKvwyug9Rr_GJI7TlFO5q6IydN2h8Yx14n62McD9xVR0qkN11T8Qu9v5LrecM_DZDRjWu0wMNLoSKoXcLpPO_p-LUnTZgc8I9zGwlkfMpQ8lTvjbKztR_-Bts7bt_ApEzEUVb0JZbqTcByPiy0SxlNyAl89HWSqz8blyjZl_55O2YMG5RZMKSvXTRPCE9gFY0H9UZ-19J3Cn-PbtBO83tRcdmKqNG6pcFnBLJiOU_EWaMurvXxyRuby4I_AJbgkq5JywT9vsJdL3RdfcZ3exw4grGLqRC5dk4KBw-bJnVMAq59RYBlymvfKsaiZJqsQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:58 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -197,18 +250,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/f70af89fbb744fef8c36ddcb1e276d85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pGSNiPCPT_kqy9G3ni_M5gamqEwo1UGp92dPdfYhm5Mkx-cZ3hdcxI62J11L1MH1DL-13Op9voGxXGQAf5IQuCfzQocN0mALN_cLNKLa-boPVoRnOPEVz7rnPY3d61k9TM9y8m4iDN3xqDTzmqCz2SDnvBVwzy9qaej8TYQaoLJLCrR9A5ivV1RtQNXujCHKUudMYb8NxD30XgBMAElNqUtcys-e9Vt9bgHRm8XUUyN93cofCVILE8OpYvHULX_iDDp600reVYm48msdWbXw8fDF9-fgZgCgprNJhJKr2eJAQC44kTw0h29-qs5tviCRn2gKVijYex9xsnxzPYSbuw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/350c3682322d43699c82246ed3edda13","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qlPGPm7j0TccEn4i2RVJhjDgy2AN4Ge9hwRxnr-MU5No_38e7Wr7CB8wVsojs_XAS1VaSu2XHmxvJK1nmtGmX0StZV-PXXW5ZxM2i-Lhuqz4WOiO22F1DReg48ZuvWJkMG6noHpRtffm7Xu83ewZGuel75WgvCogDY5cb59HLHFHfKb6dVPIKlfWEjrL_RtVKJIpw1Xa1THOjZbBoAUQ3qMnInF0tYfbOYnsgyTZL25Yjn0RfPhMPfrVZxAccNCHUGEu--opjMI0x50c27tlO1hVHCTz0k876IrictY8gVj2jYUP9_VySpIoudKEa38_SGv-ihy0TrAiB5g0_lrXUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:59 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -249,18 +302,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/22fa78d912114928a664c49e149a61e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wpbsPgBem6z38ZBNnnUBekNxZtdgIRlQNnMADvZjkAGsouoe5DXN4guxedSRS4Kz62-wnxuVWivy7r8tpuXueOMBfInc8XAxwdkqk1ODkaFAO0MYQxB1jQgMjC_A68HI-zGFIQ1BawmAxqZJdPoZJRKYuF5J978of1Ktcm-REjwOMpgT1TYoFUDvg-mMmX33kPzh1-yN8QJVE0dR6caYeE3jaborzdOa6FJbgJeOiykXleWFU3hOita7Fgg03mdShM0q7B8lzKsv0OqHZ5voeAqEzdSMJMWFEO3fZXLl8Nqx70sTugxwvi9gAIhJrWJSDffFe-QGOxGnHwBwJ82PZQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/b49135b824e7496db7fefdfce5bbc0fe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pivgU9LzvAPFSmBXrNRek6HfwOXILx-6V3zqzVggxYbnE4b1luofMnTPvMgUJ_cxCGFxE1ikUoulpGtTfZhNn3tJouYDwfFON8EdY-ATb6haFnCOoCKbRM-sO98WahwxzAqjYVQcSzm24pqf_L491wkKAPrXHwLi-SKmR3PxXoYqSvqCOir8zJ_TpkUNgB8VH-4lsCByGHOnCdbt0aUZKaqKI2dEa9DaWuyZoohnUb3uK3MKb0po3OBd3zlXoyYEFiTSTUJoy4mom7DoayQUemhuYlkmAr9Hozu0gQeDXevdp_iiP64wFgEoCkidEo0G5bjFkxSB8c8GdpMe5BSVlQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:59 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -301,18 +354,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/2be0ff9b18f74aebb1e52870ba242f61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0mgKvd7dkraprBpmV9Wg-aFE8FomA0XixIdEJdyTGwp7f1jquak2FpZdqaO_sm-z8GoyNLVDRdPeAD6GBFQloKEWm_Et88H96LbHcVp9-2Kxa4szI8zK93gzJkO0NUY4D4KNxV6KpMqL-lOgdj6Nptpg8cpbkB1vy8lrjnUGph9N0kD5iLCH6NOv1hB-RQERLp7WSn5Cz0w4OAZLGcn7tf1tkRmUf92_MSn7PUQfWx8RiDO_BL_Ak-xYiRSV3_cJeB47DDvNI34yAtagy0DbgBIzfLdXm2vUzyexH5keUrSGhA20UMdh705PngsBexNqYoQ5g_nYwE9fgKi18Ji1BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/46cc8a4557a74bb3a604f5e3f867cd10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6WerR5TvkIrPaLWr-Dv4__osCZ8LbA_N1BUA06jIcPtGE9vgBfXj9O72dOU-N2Wz2DRK1oB3rmObH_tzdBiN86B-0NG1N10ODy3u2r8I4kQvFFH2e7pWzq-1IAa6pZ37yUAUjI0v3JE3E4vqsRkdZkuigU22Ud82c5HsU1-XMXMlU5RaBYPWF-UEYS5jXt9j0vipQFqTWAT36WhBcL7rwjNsFAXeBSLFuHhp1UKKijqiSSWTLC4SN0zobAGkGinPoKTLdrofCQi6P7RheNhAloXLI-4MaZEN1iqlgpBHFQn-bY2WIHmMXMNcOom_0BQpNuEZKYfnKhSQAznQ7bMz0w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:59 GMT + - Tue, 09 Jul 2019 20:21:51 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -375,12 +428,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key5?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1560294780,"scheduledPurgeDate":1568070780,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/22fa78d912114928a664c49e149a61e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wpbsPgBem6z38ZBNnnUBekNxZtdgIRlQNnMADvZjkAGsouoe5DXN4guxedSRS4Kz62-wnxuVWivy7r8tpuXueOMBfInc8XAxwdkqk1ODkaFAO0MYQxB1jQgMjC_A68HI-zGFIQ1BawmAxqZJdPoZJRKYuF5J978of1Ktcm-REjwOMpgT1TYoFUDvg-mMmX33kPzh1-yN8QJVE0dR6caYeE3jaborzdOa6FJbgJeOiykXleWFU3hOita7Fgg03mdShM0q7B8lzKsv0OqHZ5voeAqEzdSMJMWFEO3fZXLl8Nqx70sTugxwvi9gAIhJrWJSDffFe-QGOxGnHwBwJ82PZQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/d8dbc4e087ef4d68bda02e39f4508841","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v5TQVWQ9SES-bYzE6C6G97PTKvwyug9Rr_GJI7TlFO5q6IydN2h8Yx14n62McD9xVR0qkN11T8Qu9v5LrecM_DZDRjWu0wMNLoSKoXcLpPO_p-LUnTZgc8I9zGwlkfMpQ8lTvjbKztR_-Bts7bt_ApEzEUVb0JZbqTcByPiy0SxlNyAl89HWSqz8blyjZl_55O2YMG5RZMKSvXTRPCE9gFY0H9UZ-19J3Cn-PbtBO83tRcdmKqNG6pcFnBLJiOU_EWaMurvXxyRuby4I_AJbgkq5JywT9vsJdL3RdfcZ3exw4grGLqRC5dk4KBw-bJnVMAq59RYBlymvfKsaiZJqsQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -389,7 +442,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:12:59 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -403,11 +456,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -425,12 +478,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key4?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/f70af89fbb744fef8c36ddcb1e276d85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pGSNiPCPT_kqy9G3ni_M5gamqEwo1UGp92dPdfYhm5Mkx-cZ3hdcxI62J11L1MH1DL-13Op9voGxXGQAf5IQuCfzQocN0mALN_cLNKLa-boPVoRnOPEVz7rnPY3d61k9TM9y8m4iDN3xqDTzmqCz2SDnvBVwzy9qaej8TYQaoLJLCrR9A5ivV1RtQNXujCHKUudMYb8NxD30XgBMAElNqUtcys-e9Vt9bgHRm8XUUyN93cofCVILE8OpYvHULX_iDDp600reVYm48msdWbXw8fDF9-fgZgCgprNJhJKr2eJAQC44kTw0h29-qs5tviCRn2gKVijYex9xsnxzPYSbuw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/600f30806c034e28bc148068f71991b2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pqZlmGmsTjk6--txP0bgy394cVPDEKiczm5GT0tPlwINGZeRRycLiHUmdvEyEKHRCDlYQO5lMIKh4_1q0AI_hKAt8CbClx5mDI5mjW4Bd6fiGn1GVPxhOtan8pbMQ9ADdliZvha0qA6vhDfjG-OdR2vKdNHvb9P841lNymq_kDrqyt1plha3A7WoHUkZW3_zWoxCWfFHz4A3vFRIsYNRpXbJeTKTEViXiQjCkLZTHntjqJfPIXdicooLER-2c4TpLNAtHsqmBWAm83BJnJ-Csyb1oGP2jpGW7X9wxa6MYiygAl4Y26e50O3fIOHxBEGcOkZl_d_piuy_HbwQYrS4Hw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -439,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:00 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -453,11 +506,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -475,12 +528,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vaulta7710b8a.vault.azure.net/keys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/5994595017e34fefb0faacc6ebba0ad0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pvz2unI2zFwKTCfAlEMIvYjpDY-IdpRDVZgqIQgtMufQf9xd-n4P76Fv3i0-oV7ZJu9F85NZlBvL9McVzT1UiuNM_nR80mYWV8MxsCyAOMOURtlk7E1xBOBrelq-WeRRx1pvRq1NmQHc8X5Y0-DLLS_0QMYmL2yWo2csIeCvy9llzF3_sv0jG1UpkA7FeOGw7vBb0oFk_ZSXe07b5r-QL0hPU7ft2s6QrB7Mz012qZvEm9TFSVvI3Ekmx2m16Pcev3QX4hifKKKZ8ug8b927gIYcN1FkqVRuClXAkYAa363KNYlLnQIdJFzokni_3-jrN9dlFgWj8vY4SZcVCPPdVw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/2234742e6f39413f81bed92ad0c77b61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sniM0631T66BBT5MJLD0RqPUeqR-KbZ1CeCrySxc0AYtd5DeRiUmnvAAHjjovafGMYKOEYvt-CdcL_HzfzoCoQFRcUCuGWaBbhEGPkEbZSCvT9rpLQGAqI-WxsqaifgjvQtyXe_LeZfr0_rqQT8r1sPPWncfKd9JqvgNSdYTPp0hxbKMS-hVPbdor6CmLolNuIAVxWgmFntxAJXxnvdh64iGKKytewwWOkQFw7Q_5hLrD-BqwTu-j9wVMK48URXcl4K6bbQcBk7az8KL15we8hNz_VYYbO32N36BlTNL85XRLxM4vadEaY58Qcg4iZOAlFMTH5LkAxcYes6L5bl4nw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -489,7 +542,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:00 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -503,11 +556,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -525,12 +578,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key2?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/2f6cc1c870154e858fd9da6ce6bee500","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"msh2ewKKNPjus6qtMnXJdouK4HYpLUcZvk6I3K53exIz8mKgwURfyGHb_1_SofwdThi68dACpQVh_8t6NgPYQwIK3RDviDlsEM9-1FohYx-OuLf60ciZXImMNHIqhGggdpzbpbvTtVJx54DrYLBqh0Gpvql3I3W6YiKOBDxOXt1MG10IkTFk4G2y_I0NoKgIYwxTkQTds2pVvniDMWcSbbuB09BZOz0ST62rhO2TUa2mLe7_ZWj09OayR23R5vumGobbLMLSYRTWXhn_WKh4Sg6XzgwzpTVSpIfaD27ppoQJpGy6qRETANlTKIurGqMGq880AXes5MIIK4fHLPzEaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/fb5a7e7a96b74d03b9aeea58d8f85726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wtMCrNE3c3WAQHeiWDSo6O6S3Ru__rbgGOwwcL6fxhhLt0vhbUsRP93rmZI1adjd3PE3o-8priHBnPsC__cJriDnvqfKZVA_iWJhQ5U6cXiINu1R2rlGh7gD3YsRrhzpRTRFdxhaQ9pa-0G3mGe6jMBOAujRW3Ch9HaB6yAE5fYL65WWrvpmshTIUeJZuTMGVvyCNgdQmSV472oF_E_k3CpLBhUj5hbUlNr59JhLonTm0uuq0FMm5jaE-caRcOfrGYFVYTVWXLygj25JF2lbM79Qu87n75r1BoLbk5Jcot8N14GR5KYKyTRGVdc_sMpLWuljTMUzmy0tylCZtKJPiQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -539,7 +592,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:00 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -553,11 +606,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -575,12 +628,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key0?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/c7a911d13a1245cda1b2424fef3c1b9d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1tnHqcAuo1Kvcz89mrSxqTU7H4J7tpr_aCejPHAltr2KFWKMkqpZ2ixaSyfZTYZX-Hi1hBoxBT-Xl_1T9_nlCM_aV_rua4tlz4XouNQI5GoFW85Alavj03yJ86UmEKnvHAe4QFvW-WCrJSWhUe-St7fWqzAEO5666YRmBaJRu1c9dJGNSoagbMzPfD4Ni8FgJr3Y1Ql9Cxx6EwxvcRV4r6kC8alzIJNzYwRrnFyJeBPDIKnuo1qVnMbXC2fLWxiA8aFICojisWQzpAVnsNDvVs26_6CHprqencRxAArsTNZtjpF45v98g-pxtOXXHpHEIR27WLciohhQ9LwZqVVsRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/46cc8a4557a74bb3a604f5e3f867cd10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6WerR5TvkIrPaLWr-Dv4__osCZ8LbA_N1BUA06jIcPtGE9vgBfXj9O72dOU-N2Wz2DRK1oB3rmObH_tzdBiN86B-0NG1N10ODy3u2r8I4kQvFFH2e7pWzq-1IAa6pZ37yUAUjI0v3JE3E4vqsRkdZkuigU22Ud82c5HsU1-XMXMlU5RaBYPWF-UEYS5jXt9j0vipQFqTWAT36WhBcL7rwjNsFAXeBSLFuHhp1UKKijqiSSWTLC4SN0zobAGkGinPoKTLdrofCQi6P7RheNhAloXLI-4MaZEN1iqlgpBHFQn-bY2WIHmMXMNcOom_0BQpNuEZKYfnKhSQAznQ7bMz0w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -589,7 +642,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:01 GMT + - Tue, 09 Jul 2019 20:21:52 GMT expires: - '-1' pragma: @@ -603,11 +656,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -625,12 +678,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key6?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/2be0ff9b18f74aebb1e52870ba242f61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0mgKvd7dkraprBpmV9Wg-aFE8FomA0XixIdEJdyTGwp7f1jquak2FpZdqaO_sm-z8GoyNLVDRdPeAD6GBFQloKEWm_Et88H96LbHcVp9-2Kxa4szI8zK93gzJkO0NUY4D4KNxV6KpMqL-lOgdj6Nptpg8cpbkB1vy8lrjnUGph9N0kD5iLCH6NOv1hB-RQERLp7WSn5Cz0w4OAZLGcn7tf1tkRmUf92_MSn7PUQfWx8RiDO_BL_Ak-xYiRSV3_cJeB47DDvNI34yAtagy0DbgBIzfLdXm2vUzyexH5keUrSGhA20UMdh705PngsBexNqYoQ5g_nYwE9fgKi18Ji1BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/b49135b824e7496db7fefdfce5bbc0fe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pivgU9LzvAPFSmBXrNRek6HfwOXILx-6V3zqzVggxYbnE4b1luofMnTPvMgUJ_cxCGFxE1ikUoulpGtTfZhNn3tJouYDwfFON8EdY-ATb6haFnCOoCKbRM-sO98WahwxzAqjYVQcSzm24pqf_L491wkKAPrXHwLi-SKmR3PxXoYqSvqCOir8zJ_TpkUNgB8VH-4lsCByGHOnCdbt0aUZKaqKI2dEa9DaWuyZoohnUb3uK3MKb0po3OBd3zlXoyYEFiTSTUJoy4mom7DoayQUemhuYlkmAr9Hozu0gQeDXevdp_iiP64wFgEoCkidEo0G5bjFkxSB8c8GdpMe5BSVlQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -639,7 +692,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:01 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -653,11 +706,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -675,12 +728,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta7710b8a.vault.azure.net/keys/key3?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/keys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/ddacf2949c184f3781e184b3b7829ec6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ss8mYFywgnYUqIb9rE_-FVetI2TyNi1Ht0lmaPu0Y0aXAkMPFywrhoDdeRZdyd4H7SwG9CY7oIyc2Ch4KqZdCBJY5d3CUgQgcNg7P98tG8mmR8TMwsnh2WBChoXRny2u2ySPAPDwGBPQsu-00tRLe8e_ITMskdY-x-c7dKUjK97oJJb9Kh5atJL88EjpRBYh__V9qTqoXUKy83I2sg0aiLmSnk6L3YMJqAdgMnHMAYoZfzxSy4_gjZFC5a7CrvClxYKdWYzlk40JF3_0BHcrEvaDn6gY6IQfYF-tNKPGKHqw3MreTVNQNTPhpSnuhszFfu9giEub6HM47lx-_KaXoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1562703714,"scheduledPurgeDate":1570479714,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/350c3682322d43699c82246ed3edda13","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qlPGPm7j0TccEn4i2RVJhjDgy2AN4Ge9hwRxnr-MU5No_38e7Wr7CB8wVsojs_XAS1VaSu2XHmxvJK1nmtGmX0StZV-PXXW5ZxM2i-Lhuqz4WOiO22F1DReg48ZuvWJkMG6noHpRtffm7Xu83ewZGuel75WgvCogDY5cb59HLHFHfKb6dVPIKlfWEjrL_RtVKJIpw1Xa1THOjZbBoAUQ3qMnInF0tYfbOYnsgyTZL25Yjn0RfPhMPfrVZxAccNCHUGEu--opjMI0x50c27tlO1hVHCTz0k876IrictY8gVj2jYUP9_VySpIoudKEa38_SGv-ihy0TrAiB5g0_lrXUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -689,7 +742,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:01 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -703,11 +756,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -723,12 +776,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key5"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -737,7 +791,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:01 GMT + - Tue, 09 Jul 2019 20:21:53 GMT expires: - '-1' pragma: @@ -751,11 +805,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -771,12 +825,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key5"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' headers: cache-control: - no-cache @@ -785,7 +840,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:05 GMT + - Tue, 09 Jul 2019 20:21:57 GMT expires: - '-1' pragma: @@ -799,11 +854,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -819,12 +874,110 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1560294780,"scheduledPurgeDate":1568070780,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/22fa78d912114928a664c49e149a61e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wpbsPgBem6z38ZBNnnUBekNxZtdgIRlQNnMADvZjkAGsouoe5DXN4guxedSRS4Kz62-wnxuVWivy7r8tpuXueOMBfInc8XAxwdkqk1ODkaFAO0MYQxB1jQgMjC_A68HI-zGFIQ1BawmAxqZJdPoZJRKYuF5J978of1Ktcm-REjwOMpgT1TYoFUDvg-mMmX33kPzh1-yN8QJVE0dR6caYeE3jaborzdOa6FJbgJeOiykXleWFU3hOita7Fgg03mdShM0q7B8lzKsv0OqHZ5voeAqEzdSMJMWFEO3fZXLl8Nqx70sTugxwvi9gAIhJrWJSDffFe-QGOxGnHwBwJ82PZQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' + headers: + cache-control: + - no-cache + content-length: + - '72' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:22:00 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 + response: + body: + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key3"}}' + headers: + cache-control: + - no-cache + content-length: + - '72' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:22:03 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 + response: + body: + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/d8dbc4e087ef4d68bda02e39f4508841","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v5TQVWQ9SES-bYzE6C6G97PTKvwyug9Rr_GJI7TlFO5q6IydN2h8Yx14n62McD9xVR0qkN11T8Qu9v5LrecM_DZDRjWu0wMNLoSKoXcLpPO_p-LUnTZgc8I9zGwlkfMpQ8lTvjbKztR_-Bts7bt_ApEzEUVb0JZbqTcByPiy0SxlNyAl89HWSqz8blyjZl_55O2YMG5RZMKSvXTRPCE9gFY0H9UZ-19J3Cn-PbtBO83tRcdmKqNG6pcFnBLJiOU_EWaMurvXxyRuby4I_AJbgkq5JywT9vsJdL3RdfcZ3exw4grGLqRC5dk4KBw-bJnVMAq59RYBlymvfKsaiZJqsQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -833,7 +986,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:09 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -847,11 +1000,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -867,12 +1020,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key4?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/f70af89fbb744fef8c36ddcb1e276d85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pGSNiPCPT_kqy9G3ni_M5gamqEwo1UGp92dPdfYhm5Mkx-cZ3hdcxI62J11L1MH1DL-13Op9voGxXGQAf5IQuCfzQocN0mALN_cLNKLa-boPVoRnOPEVz7rnPY3d61k9TM9y8m4iDN3xqDTzmqCz2SDnvBVwzy9qaej8TYQaoLJLCrR9A5ivV1RtQNXujCHKUudMYb8NxD30XgBMAElNqUtcys-e9Vt9bgHRm8XUUyN93cofCVILE8OpYvHULX_iDDp600reVYm48msdWbXw8fDF9-fgZgCgprNJhJKr2eJAQC44kTw0h29-qs5tviCRn2gKVijYex9xsnxzPYSbuw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/600f30806c034e28bc148068f71991b2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pqZlmGmsTjk6--txP0bgy394cVPDEKiczm5GT0tPlwINGZeRRycLiHUmdvEyEKHRCDlYQO5lMIKh4_1q0AI_hKAt8CbClx5mDI5mjW4Bd6fiGn1GVPxhOtan8pbMQ9ADdliZvha0qA6vhDfjG-OdR2vKdNHvb9P841lNymq_kDrqyt1plha3A7WoHUkZW3_zWoxCWfFHz4A3vFRIsYNRpXbJeTKTEViXiQjCkLZTHntjqJfPIXdicooLER-2c4TpLNAtHsqmBWAm83BJnJ-Csyb1oGP2jpGW7X9wxa6MYiygAl4Y26e50O3fIOHxBEGcOkZl_d_piuy_HbwQYrS4Hw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -881,7 +1034,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:09 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -895,11 +1048,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -915,12 +1068,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/5994595017e34fefb0faacc6ebba0ad0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pvz2unI2zFwKTCfAlEMIvYjpDY-IdpRDVZgqIQgtMufQf9xd-n4P76Fv3i0-oV7ZJu9F85NZlBvL9McVzT1UiuNM_nR80mYWV8MxsCyAOMOURtlk7E1xBOBrelq-WeRRx1pvRq1NmQHc8X5Y0-DLLS_0QMYmL2yWo2csIeCvy9llzF3_sv0jG1UpkA7FeOGw7vBb0oFk_ZSXe07b5r-QL0hPU7ft2s6QrB7Mz012qZvEm9TFSVvI3Ekmx2m16Pcev3QX4hifKKKZ8ug8b927gIYcN1FkqVRuClXAkYAa363KNYlLnQIdJFzokni_3-jrN9dlFgWj8vY4SZcVCPPdVw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/2234742e6f39413f81bed92ad0c77b61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sniM0631T66BBT5MJLD0RqPUeqR-KbZ1CeCrySxc0AYtd5DeRiUmnvAAHjjovafGMYKOEYvt-CdcL_HzfzoCoQFRcUCuGWaBbhEGPkEbZSCvT9rpLQGAqI-WxsqaifgjvQtyXe_LeZfr0_rqQT8r1sPPWncfKd9JqvgNSdYTPp0hxbKMS-hVPbdor6CmLolNuIAVxWgmFntxAJXxnvdh64iGKKytewwWOkQFw7Q_5hLrD-BqwTu-j9wVMK48URXcl4K6bbQcBk7az8KL15we8hNz_VYYbO32N36BlTNL85XRLxM4vadEaY58Qcg4iZOAlFMTH5LkAxcYes6L5bl4nw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -929,7 +1082,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:09 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -943,11 +1096,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -963,12 +1116,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key2?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/2f6cc1c870154e858fd9da6ce6bee500","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"msh2ewKKNPjus6qtMnXJdouK4HYpLUcZvk6I3K53exIz8mKgwURfyGHb_1_SofwdThi68dACpQVh_8t6NgPYQwIK3RDviDlsEM9-1FohYx-OuLf60ciZXImMNHIqhGggdpzbpbvTtVJx54DrYLBqh0Gpvql3I3W6YiKOBDxOXt1MG10IkTFk4G2y_I0NoKgIYwxTkQTds2pVvniDMWcSbbuB09BZOz0ST62rhO2TUa2mLe7_ZWj09OayR23R5vumGobbLMLSYRTWXhn_WKh4Sg6XzgwzpTVSpIfaD27ppoQJpGy6qRETANlTKIurGqMGq880AXes5MIIK4fHLPzEaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/fb5a7e7a96b74d03b9aeea58d8f85726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wtMCrNE3c3WAQHeiWDSo6O6S3Ru__rbgGOwwcL6fxhhLt0vhbUsRP93rmZI1adjd3PE3o-8priHBnPsC__cJriDnvqfKZVA_iWJhQ5U6cXiINu1R2rlGh7gD3YsRrhzpRTRFdxhaQ9pa-0G3mGe6jMBOAujRW3Ch9HaB6yAE5fYL65WWrvpmshTIUeJZuTMGVvyCNgdQmSV472oF_E_k3CpLBhUj5hbUlNr59JhLonTm0uuq0FMm5jaE-caRcOfrGYFVYTVWXLygj25JF2lbM79Qu87n75r1BoLbk5Jcot8N14GR5KYKyTRGVdc_sMpLWuljTMUzmy0tylCZtKJPiQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -977,7 +1130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:09 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -991,11 +1144,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1011,12 +1164,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/c7a911d13a1245cda1b2424fef3c1b9d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1tnHqcAuo1Kvcz89mrSxqTU7H4J7tpr_aCejPHAltr2KFWKMkqpZ2ixaSyfZTYZX-Hi1hBoxBT-Xl_1T9_nlCM_aV_rua4tlz4XouNQI5GoFW85Alavj03yJ86UmEKnvHAe4QFvW-WCrJSWhUe-St7fWqzAEO5666YRmBaJRu1c9dJGNSoagbMzPfD4Ni8FgJr3Y1Ql9Cxx6EwxvcRV4r6kC8alzIJNzYwRrnFyJeBPDIKnuo1qVnMbXC2fLWxiA8aFICojisWQzpAVnsNDvVs26_6CHprqencRxAArsTNZtjpF45v98g-pxtOXXHpHEIR27WLciohhQ9LwZqVVsRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/46cc8a4557a74bb3a604f5e3f867cd10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6WerR5TvkIrPaLWr-Dv4__osCZ8LbA_N1BUA06jIcPtGE9vgBfXj9O72dOU-N2Wz2DRK1oB3rmObH_tzdBiN86B-0NG1N10ODy3u2r8I4kQvFFH2e7pWzq-1IAa6pZ37yUAUjI0v3JE3E4vqsRkdZkuigU22Ud82c5HsU1-XMXMlU5RaBYPWF-UEYS5jXt9j0vipQFqTWAT36WhBcL7rwjNsFAXeBSLFuHhp1UKKijqiSSWTLC4SN0zobAGkGinPoKTLdrofCQi6P7RheNhAloXLI-4MaZEN1iqlgpBHFQn-bY2WIHmMXMNcOom_0BQpNuEZKYfnKhSQAznQ7bMz0w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1025,7 +1178,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:10 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1039,11 +1192,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1059,12 +1212,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key6?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/2be0ff9b18f74aebb1e52870ba242f61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0mgKvd7dkraprBpmV9Wg-aFE8FomA0XixIdEJdyTGwp7f1jquak2FpZdqaO_sm-z8GoyNLVDRdPeAD6GBFQloKEWm_Et88H96LbHcVp9-2Kxa4szI8zK93gzJkO0NUY4D4KNxV6KpMqL-lOgdj6Nptpg8cpbkB1vy8lrjnUGph9N0kD5iLCH6NOv1hB-RQERLp7WSn5Cz0w4OAZLGcn7tf1tkRmUf92_MSn7PUQfWx8RiDO_BL_Ak-xYiRSV3_cJeB47DDvNI34yAtagy0DbgBIzfLdXm2vUzyexH5keUrSGhA20UMdh705PngsBexNqYoQ5g_nYwE9fgKi18Ji1BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/b49135b824e7496db7fefdfce5bbc0fe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pivgU9LzvAPFSmBXrNRek6HfwOXILx-6V3zqzVggxYbnE4b1luofMnTPvMgUJ_cxCGFxE1ikUoulpGtTfZhNn3tJouYDwfFON8EdY-ATb6haFnCOoCKbRM-sO98WahwxzAqjYVQcSzm24pqf_L491wkKAPrXHwLi-SKmR3PxXoYqSvqCOir8zJ_TpkUNgB8VH-4lsCByGHOnCdbt0aUZKaqKI2dEa9DaWuyZoohnUb3uK3MKb0po3OBd3zlXoyYEFiTSTUJoy4mom7DoayQUemhuYlkmAr9Hozu0gQeDXevdp_iiP64wFgEoCkidEo0G5bjFkxSB8c8GdpMe5BSVlQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1073,7 +1226,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:10 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1087,11 +1240,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1107,12 +1260,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/ddacf2949c184f3781e184b3b7829ec6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ss8mYFywgnYUqIb9rE_-FVetI2TyNi1Ht0lmaPu0Y0aXAkMPFywrhoDdeRZdyd4H7SwG9CY7oIyc2Ch4KqZdCBJY5d3CUgQgcNg7P98tG8mmR8TMwsnh2WBChoXRny2u2ySPAPDwGBPQsu-00tRLe8e_ITMskdY-x-c7dKUjK97oJJb9Kh5atJL88EjpRBYh__V9qTqoXUKy83I2sg0aiLmSnk6L3YMJqAdgMnHMAYoZfzxSy4_gjZFC5a7CrvClxYKdWYzlk40JF3_0BHcrEvaDn6gY6IQfYF-tNKPGKHqw3MreTVNQNTPhpSnuhszFfu9giEub6HM47lx-_KaXoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1562703714,"scheduledPurgeDate":1570479714,"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/350c3682322d43699c82246ed3edda13","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qlPGPm7j0TccEn4i2RVJhjDgy2AN4Ge9hwRxnr-MU5No_38e7Wr7CB8wVsojs_XAS1VaSu2XHmxvJK1nmtGmX0StZV-PXXW5ZxM2i-Lhuqz4WOiO22F1DReg48ZuvWJkMG6noHpRtffm7Xu83ewZGuel75WgvCogDY5cb59HLHFHfKb6dVPIKlfWEjrL_RtVKJIpw1Xa1THOjZbBoAUQ3qMnInF0tYfbOYnsgyTZL25Yjn0RfPhMPfrVZxAccNCHUGEu--opjMI0x50c27tlO1hVHCTz0k876IrictY8gVj2jYUP9_VySpIoudKEa38_SGv-ihy0TrAiB5g0_lrXUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1121,7 +1274,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:10 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1135,11 +1288,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1155,12 +1308,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta7710b8a.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1560294781,"scheduledPurgeDate":1568070781,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1560294780,"scheduledPurgeDate":1568070780,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1560294782,"scheduledPurgeDate":1568070782,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key0","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key1","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key2","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key3","deletedDate":1562703712,"scheduledPurgeDate":1570479712,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key4","deletedDate":1562703714,"scheduledPurgeDate":1570479714,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key5","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}},{"recoveryId":"https://vaulta7710b8a.vault.azure.net/deletedkeys/key6","deletedDate":1562703713,"scheduledPurgeDate":1570479713,"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -1169,7 +1322,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:10 GMT + - Tue, 09 Jul 2019 20:22:06 GMT expires: - '-1' pragma: @@ -1183,11 +1336,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1205,12 +1358,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/22fa78d912114928a664c49e149a61e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wpbsPgBem6z38ZBNnnUBekNxZtdgIRlQNnMADvZjkAGsouoe5DXN4guxedSRS4Kz62-wnxuVWivy7r8tpuXueOMBfInc8XAxwdkqk1ODkaFAO0MYQxB1jQgMjC_A68HI-zGFIQ1BawmAxqZJdPoZJRKYuF5J978of1Ktcm-REjwOMpgT1TYoFUDvg-mMmX33kPzh1-yN8QJVE0dR6caYeE3jaborzdOa6FJbgJeOiykXleWFU3hOita7Fgg03mdShM0q7B8lzKsv0OqHZ5voeAqEzdSMJMWFEO3fZXLl8Nqx70sTugxwvi9gAIhJrWJSDffFe-QGOxGnHwBwJ82PZQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/d8dbc4e087ef4d68bda02e39f4508841","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v5TQVWQ9SES-bYzE6C6G97PTKvwyug9Rr_GJI7TlFO5q6IydN2h8Yx14n62McD9xVR0qkN11T8Qu9v5LrecM_DZDRjWu0wMNLoSKoXcLpPO_p-LUnTZgc8I9zGwlkfMpQ8lTvjbKztR_-Bts7bt_ApEzEUVb0JZbqTcByPiy0SxlNyAl89HWSqz8blyjZl_55O2YMG5RZMKSvXTRPCE9gFY0H9UZ-19J3Cn-PbtBO83tRcdmKqNG6pcFnBLJiOU_EWaMurvXxyRuby4I_AJbgkq5JywT9vsJdL3RdfcZ3exw4grGLqRC5dk4KBw-bJnVMAq59RYBlymvfKsaiZJqsQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1219,7 +1372,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:10 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1233,11 +1386,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1255,12 +1408,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key4/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key2/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/f70af89fbb744fef8c36ddcb1e276d85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pGSNiPCPT_kqy9G3ni_M5gamqEwo1UGp92dPdfYhm5Mkx-cZ3hdcxI62J11L1MH1DL-13Op9voGxXGQAf5IQuCfzQocN0mALN_cLNKLa-boPVoRnOPEVz7rnPY3d61k9TM9y8m4iDN3xqDTzmqCz2SDnvBVwzy9qaej8TYQaoLJLCrR9A5ivV1RtQNXujCHKUudMYb8NxD30XgBMAElNqUtcys-e9Vt9bgHRm8XUUyN93cofCVILE8OpYvHULX_iDDp600reVYm48msdWbXw8fDF9-fgZgCgprNJhJKr2eJAQC44kTw0h29-qs5tviCRn2gKVijYex9xsnxzPYSbuw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/600f30806c034e28bc148068f71991b2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pqZlmGmsTjk6--txP0bgy394cVPDEKiczm5GT0tPlwINGZeRRycLiHUmdvEyEKHRCDlYQO5lMIKh4_1q0AI_hKAt8CbClx5mDI5mjW4Bd6fiGn1GVPxhOtan8pbMQ9ADdliZvha0qA6vhDfjG-OdR2vKdNHvb9P841lNymq_kDrqyt1plha3A7WoHUkZW3_zWoxCWfFHz4A3vFRIsYNRpXbJeTKTEViXiQjCkLZTHntjqJfPIXdicooLER-2c4TpLNAtHsqmBWAm83BJnJ-Csyb1oGP2jpGW7X9wxa6MYiygAl4Y26e50O3fIOHxBEGcOkZl_d_piuy_HbwQYrS4Hw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1269,7 +1422,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:11 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1283,11 +1436,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1305,12 +1458,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key1/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/5994595017e34fefb0faacc6ebba0ad0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pvz2unI2zFwKTCfAlEMIvYjpDY-IdpRDVZgqIQgtMufQf9xd-n4P76Fv3i0-oV7ZJu9F85NZlBvL9McVzT1UiuNM_nR80mYWV8MxsCyAOMOURtlk7E1xBOBrelq-WeRRx1pvRq1NmQHc8X5Y0-DLLS_0QMYmL2yWo2csIeCvy9llzF3_sv0jG1UpkA7FeOGw7vBb0oFk_ZSXe07b5r-QL0hPU7ft2s6QrB7Mz012qZvEm9TFSVvI3Ekmx2m16Pcev3QX4hifKKKZ8ug8b927gIYcN1FkqVRuClXAkYAa363KNYlLnQIdJFzokni_3-jrN9dlFgWj8vY4SZcVCPPdVw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key1/2234742e6f39413f81bed92ad0c77b61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sniM0631T66BBT5MJLD0RqPUeqR-KbZ1CeCrySxc0AYtd5DeRiUmnvAAHjjovafGMYKOEYvt-CdcL_HzfzoCoQFRcUCuGWaBbhEGPkEbZSCvT9rpLQGAqI-WxsqaifgjvQtyXe_LeZfr0_rqQT8r1sPPWncfKd9JqvgNSdYTPp0hxbKMS-hVPbdor6CmLolNuIAVxWgmFntxAJXxnvdh64iGKKytewwWOkQFw7Q_5hLrD-BqwTu-j9wVMK48URXcl4K6bbQcBk7az8KL15we8hNz_VYYbO32N36BlTNL85XRLxM4vadEaY58Qcg4iZOAlFMTH5LkAxcYes6L5bl4nw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1319,7 +1472,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:11 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1333,11 +1486,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1355,12 +1508,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key2/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key0/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key2/2f6cc1c870154e858fd9da6ce6bee500","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"msh2ewKKNPjus6qtMnXJdouK4HYpLUcZvk6I3K53exIz8mKgwURfyGHb_1_SofwdThi68dACpQVh_8t6NgPYQwIK3RDviDlsEM9-1FohYx-OuLf60ciZXImMNHIqhGggdpzbpbvTtVJx54DrYLBqh0Gpvql3I3W6YiKOBDxOXt1MG10IkTFk4G2y_I0NoKgIYwxTkQTds2pVvniDMWcSbbuB09BZOz0ST62rhO2TUa2mLe7_ZWj09OayR23R5vumGobbLMLSYRTWXhn_WKh4Sg6XzgwzpTVSpIfaD27ppoQJpGy6qRETANlTKIurGqMGq880AXes5MIIK4fHLPzEaQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/fb5a7e7a96b74d03b9aeea58d8f85726","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wtMCrNE3c3WAQHeiWDSo6O6S3Ru__rbgGOwwcL6fxhhLt0vhbUsRP93rmZI1adjd3PE3o-8priHBnPsC__cJriDnvqfKZVA_iWJhQ5U6cXiINu1R2rlGh7gD3YsRrhzpRTRFdxhaQ9pa-0G3mGe6jMBOAujRW3Ch9HaB6yAE5fYL65WWrvpmshTIUeJZuTMGVvyCNgdQmSV472oF_E_k3CpLBhUj5hbUlNr59JhLonTm0uuq0FMm5jaE-caRcOfrGYFVYTVWXLygj25JF2lbM79Qu87n75r1BoLbk5Jcot8N14GR5KYKyTRGVdc_sMpLWuljTMUzmy0tylCZtKJPiQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703710,"updated":1562703710,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1369,7 +1522,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:11 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1383,11 +1536,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1405,12 +1558,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key0/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key6/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key0/c7a911d13a1245cda1b2424fef3c1b9d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1tnHqcAuo1Kvcz89mrSxqTU7H4J7tpr_aCejPHAltr2KFWKMkqpZ2ixaSyfZTYZX-Hi1hBoxBT-Xl_1T9_nlCM_aV_rua4tlz4XouNQI5GoFW85Alavj03yJ86UmEKnvHAe4QFvW-WCrJSWhUe-St7fWqzAEO5666YRmBaJRu1c9dJGNSoagbMzPfD4Ni8FgJr3Y1Ql9Cxx6EwxvcRV4r6kC8alzIJNzYwRrnFyJeBPDIKnuo1qVnMbXC2fLWxiA8aFICojisWQzpAVnsNDvVs26_6CHprqencRxAArsTNZtjpF45v98g-pxtOXXHpHEIR27WLciohhQ9LwZqVVsRw","e":"AQAB"},"attributes":{"enabled":true,"created":1560294778,"updated":1560294778,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/46cc8a4557a74bb3a604f5e3f867cd10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6WerR5TvkIrPaLWr-Dv4__osCZ8LbA_N1BUA06jIcPtGE9vgBfXj9O72dOU-N2Wz2DRK1oB3rmObH_tzdBiN86B-0NG1N10ODy3u2r8I4kQvFFH2e7pWzq-1IAa6pZ37yUAUjI0v3JE3E4vqsRkdZkuigU22Ud82c5HsU1-XMXMlU5RaBYPWF-UEYS5jXt9j0vipQFqTWAT36WhBcL7rwjNsFAXeBSLFuHhp1UKKijqiSSWTLC4SN0zobAGkGinPoKTLdrofCQi6P7RheNhAloXLI-4MaZEN1iqlgpBHFQn-bY2WIHmMXMNcOom_0BQpNuEZKYfnKhSQAznQ7bMz0w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1419,7 +1572,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:11 GMT + - Tue, 09 Jul 2019 20:22:07 GMT expires: - '-1' pragma: @@ -1433,11 +1586,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1455,12 +1608,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key6/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key5/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key6/2be0ff9b18f74aebb1e52870ba242f61","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0mgKvd7dkraprBpmV9Wg-aFE8FomA0XixIdEJdyTGwp7f1jquak2FpZdqaO_sm-z8GoyNLVDRdPeAD6GBFQloKEWm_Et88H96LbHcVp9-2Kxa4szI8zK93gzJkO0NUY4D4KNxV6KpMqL-lOgdj6Nptpg8cpbkB1vy8lrjnUGph9N0kD5iLCH6NOv1hB-RQERLp7WSn5Cz0w4OAZLGcn7tf1tkRmUf92_MSn7PUQfWx8RiDO_BL_Ak-xYiRSV3_cJeB47DDvNI34yAtagy0DbgBIzfLdXm2vUzyexH5keUrSGhA20UMdh705PngsBexNqYoQ5g_nYwE9fgKi18Ji1BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294780,"updated":1560294780,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key5/b49135b824e7496db7fefdfce5bbc0fe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pivgU9LzvAPFSmBXrNRek6HfwOXILx-6V3zqzVggxYbnE4b1luofMnTPvMgUJ_cxCGFxE1ikUoulpGtTfZhNn3tJouYDwfFON8EdY-ATb6haFnCOoCKbRM-sO98WahwxzAqjYVQcSzm24pqf_L491wkKAPrXHwLi-SKmR3PxXoYqSvqCOir8zJ_TpkUNgB8VH-4lsCByGHOnCdbt0aUZKaqKI2dEa9DaWuyZoohnUb3uK3MKb0po3OBd3zlXoyYEFiTSTUJoy4mom7DoayQUemhuYlkmAr9Hozu0gQeDXevdp_iiP64wFgEoCkidEo0G5bjFkxSB8c8GdpMe5BSVlQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703712,"updated":1562703712,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1469,7 +1622,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:12 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -1483,11 +1636,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1505,12 +1658,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key3/recover?api-version=7.0 + uri: https://vaulta7710b8a.vault.azure.net/deletedkeys/key4/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key3/ddacf2949c184f3781e184b3b7829ec6","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ss8mYFywgnYUqIb9rE_-FVetI2TyNi1Ht0lmaPu0Y0aXAkMPFywrhoDdeRZdyd4H7SwG9CY7oIyc2Ch4KqZdCBJY5d3CUgQgcNg7P98tG8mmR8TMwsnh2WBChoXRny2u2ySPAPDwGBPQsu-00tRLe8e_ITMskdY-x-c7dKUjK97oJJb9Kh5atJL88EjpRBYh__V9qTqoXUKy83I2sg0aiLmSnk6L3YMJqAdgMnHMAYoZfzxSy4_gjZFC5a7CrvClxYKdWYzlk40JF3_0BHcrEvaDn6gY6IQfYF-tNKPGKHqw3MreTVNQNTPhpSnuhszFfu9giEub6HM47lx-_KaXoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560294779,"updated":1560294779,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vaulta7710b8a.vault.azure.net/keys/key4/350c3682322d43699c82246ed3edda13","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qlPGPm7j0TccEn4i2RVJhjDgy2AN4Ge9hwRxnr-MU5No_38e7Wr7CB8wVsojs_XAS1VaSu2XHmxvJK1nmtGmX0StZV-PXXW5ZxM2i-Lhuqz4WOiO22F1DReg48ZuvWJkMG6noHpRtffm7Xu83ewZGuel75WgvCogDY5cb59HLHFHfKb6dVPIKlfWEjrL_RtVKJIpw1Xa1THOjZbBoAUQ3qMnInF0tYfbOYnsgyTZL25Yjn0RfPhMPfrVZxAccNCHUGEu--opjMI0x50c27tlO1hVHCTz0k876IrictY8gVj2jYUP9_VySpIoudKEa38_SGv-ihy0TrAiB5g0_lrXUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703711,"updated":1562703711,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1519,7 +1672,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 23:13:12 GMT + - Tue, 09 Jul 2019 20:22:08 GMT expires: - '-1' pragma: @@ -1533,11 +1686,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=166.255.248.80;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_backup_restore.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_backup_restore.yaml index 3807d0b5f56c..1bdab8917b58 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_backup_restore.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_backup_restore.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:17:51 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/7cb699572d7443838ff94a8a0e79828c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMQcqtwA-nD8Gg7Px-5JnqIJUW0OIn2XFr2X4DSJrDXwzdOClqHf4miDhUE0swSJYGUsxBYfJ8in9sqC-1iqBP_dcKvGYaEhjzgEALe14tqLRSmLDj6C6rHUEUBx1nWxOZof5DEwnlDR0ywUscQgkmnfoSjWIO8w51PgxZ_NgovDfnXRZV9oDAKSfcmqguWSPE2uXCGYIdEAiLleBgfUj47W8gTzROKmaWu2hQ-e9xrQuN8KozB8j2LZfSnNvKI6-mN6_E_Son8OFtZnOv-NHYvMmGzanj5uT6Kdg3Nb0M8HQUKov7NC0_3MJGXkWjdOAjER6QZf981xx81ddHq3BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219221,"updated":1560219221,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/54248fddc148400a9df744075bbfe2e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lsgBFBFSUvohGWs21xzMCFc1IRDfnH0UlZ8aJnqVozFkc9rZgaBdr5IOVmt41hxbJY2MWuJxGpS4xAZ8AmrQZ1MOmS7nUiMmorcyqZZIcZYz3P7YXLjiv-ncb9rTty7IJ2R9v111Beiq3tNdHUrQYUt1wiysQPcPk69FGq7h4B5UqDM4SHQqXF-Rm3R2fU9wct7bx3F2XqYcaMmQWZBiYMH8vbooZWyh2QziCXVB2MhfBt0gTfoLUGT4hheDyLfIgbhhky6Hi0DC-3Ecll3tlK6JHAR6V1jLxJveWPgUy2WgRMSTATWdlcYL_i9Ji9OT4dSLkCFqXWhRN8krxXf9dw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -63,12 +116,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/backup?api-version=7.0 response: body: - string: '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLm4zcmZBbzBINl9NQW9lMHJDbm1zcXJRbkpLYVRlNVNjTndPSWF5NDZ2S1lXTDlZLXRjMFhkRklJdDlQNjlMYzVaNGNKM0dHRFVXYlhIbEVIa3ZLcVRyX0huWmN0QUZpNHB6QVRmQ2NqMG5RLU5WTG5sMzRicjBfWlQzVmlpWW5zUEJqZ0JiRmhqYUJLSXU2aXhhTXVyWjZ3eHg3NVdTSzZIc0hXZlRnUFNoZ2hLQmVUelR4ZnhRcFJUb0RDYmUzandOTXZoRHF6aDBTWlBWYlBmcEx0WEVDN1JZcFpncm10LUlGMEVLVGY5a1dER0oyOGV3WElId1NDTWtWSVZFTkpnYXhDVW9oUFBfZ0l2NE9RemxSaG93TnU3bHpJQ3RvT2UtQUFHbHBjcUZKTk1VZEkwSXY0YnFlcDUxS1NoZjFnRmp6ZGxMdGRfQTFDMTY5Y1pHTnYtUS5Ld3VSeXJFWElWWUZpNDV2MmEyX0pBLnVvZ0pRWktBX2FRdWVyY2xNOTlQbTZjVjJuTDFMcnMwbkFaa3UxR29UMjU1Y3MtcVJBUmRGYzk4bTRrdGVDd3ZOWUE1MlA4UXJMMXpXZWNEbWw0VEZZN3dvYXFXS1I3aFlJMTBQOVFWdzJMc25IZEFZYUxnb3dYdTFwaVZQSUYtaUZEb3JmY0ZVeUJQVGhCZDBhZVhJVllpd2c0SXBkNTdodHczNlE2Y0VHSVBndld4eUxkWDl4TEtBQlpuWjFTN2xZQ0VLVnJvZUl5MUVFRFRxdWRSOGZ6cXc1WE1jYXY5NXdJbmtEOVg4SGdLYWNEaE5ZSU4ycFBPc0ppWTR6Q1ppRWRCUWxzMWlYSjZNd25aem8xRE1yQ0tVWk5FNEp5aFZ1djNid01UTVlxMmN6cWJZSXpyWWNjNHpnRVI5YV9wS2x0WEpyb08zLTQxNC1jUzUyNlc1UDAxM2FjSEw2d0toOXhObi1wNXFvcEs4ajVfTVZJZ0lRSlA5V0ZuMU1VdklrZUtSNnNDOXlsa3RMVFB2M0JYb3ZRcXg2NVpRSXVPSXR1MnVIamgyUm9sS3h1NzVfMVpJd0NVSlFnNng2MFRxZ21QSElKcmtlUm1EZTI4VHVCNFh1VkxVbTcxd2JlMkVvZzk0Q3R2bUlpejBxYU9aQWJDZTVXSHlOYl9XQkE5LUNFbG5BTHZhZEx2ZTk0QkJnaU9FNnpzNTVUY3ZoQUFVM2dMSW16ZDlwaFJWMFdyMW9ycDF0dkl2dHk1SjhSaW1EOXZac2EyQ0VDYVdVWTNQLUJtb1R6cWNldlBoVWxndU5lU3gxRXVDb1NucV9wbkNTV1RFMzZIVkJLaU01LXFyYUxGR2dod29hQVhlb2xGd2lzUjI0al9RcDdJNEFiaVNLd2UwdUc5ODFCTVE1MlhXOGpDTlM3YzdZUENvTldNUV91eDlwWHU2cE1SSEVEV3RZc2ZieFY5UWVzNUp3V1FoeDl1VWU2ZU5JVS1tTlYzQ3BzOUZwQU44WXU2Z1V4d3Rxd042SEpvQ0prUHBkOF9CS1dHcmxhUzRNdUpweU1sUHBnOFpWTy01UTUwRTRTcWVOSHZlTlhhdzFxYzZlMFhzdWhEaVpfVm9LOTZ0eHFMRklzZHdxLXVIWGlSMEF6S1VLTzdxSjlhT3R1MHBCUE52U2tRWVA2UXZYRktsSkIxaDM2SUVXRFZfaElHdmVmSks2WDUxdHptbmZZZGVuamZybG5taUFQQXFDTTVUNmhWTS1jbEhicHc4Ym1adE03QmVpQURCam1rQzNab2lxOVpyNWhfaWhvUVFGLWJYenFQUHJqeUxwM19sZ2s0b191dnY1YlJlV2FzZGZFSHJiVU91MUVnUDNNNWpveTBLazllOFNQZk50bFNDNDlxdWpmVW5DTHhVWVd2VV9JWDhhdXE3S09UM3ZVRHlJemZ6ODNvaUlGaXJvbUhwbXBEUXlkWjVpR3E0NE1tLTNhZlF6ZkRxTTFrSGN1MjVWc1pxNkxMSlNKSlVDT09lMnd4Y3RMUk1NMWJrUDBET1pNcjZFMkhUNlo0QTVUcEw0Q3haT2dxVVk5N1Y2dkZRWnE3ZWo5OGpiS1ZZV0p3OU50eTVoOTRSQmZiMkFZRk52UEdXTGVmYWQzaVZncURaaE5MeDkzWFoxNUJ1anFfMlJyczZjdk9vNmkyRTZOVngzUkQzdVpYdWZ3S1JVU3BsazJCQ3FnbWlzT1FHQ1BzcHNjcVlJT1Q1UDhjeWtmUV96NXdDbGlQb21oV0lubmpHOW9CUE4tZWcyMU1KbUNmeWo2T0xvbzVtdUN5TDNnTkRJa1RpOXBKRG8xN1pJc1lBV2ZtZnNxWENuSElzMDdHRlBIdHlNZ01rQUNocGxZdmZETTdLbW9SVEdJTmRhenloSURxQXlaQW9QSENvanhxb0F5Y0NMRGM5cjJaUkpKc09INmNMVzBtQWM0eTYwdXlDSGZrak1XZ1ZoTUFENjYxLWtka0N0UEI5MHcyVmhsSWgtSzVmT1oxTWVRX1RMSUpyZGFPeFBjbkNHbnhsNnVJR0pzX1ZhTXRHUjZKT2VzUXQ3MmZYWjQtWjFKQmVEZ0U3S240RFpKWVRrQTY0Z0ZJV09vZFNCaVd6bnAwNldPejBMZU1Db0Y3cDhFZFlTQi1VZVVHMEw5a1lnWXBQLWlYRFpHTk1YY282VnFUQjR0dEo1ZjhiUzhrVEVkQkMtTGZyOW9tUXRPSDdjcWhRZ2l4ZkRScmYzeW1uQ1ZUNW94SzZvcjU2a2VjRU81ejVFQWt3aDRNQjY4MElMME1VMzNZQVV0NVZuekh2M2hncExQdDYwS1B2VVJPZlJoQmpGNmdzUTZrTzBicDlmOFJjU0RBdWdCbkl2RnZTNkNXTDN4MXRnWklZbVFVSHdJOG9ZX1k5UVg3ZU5udW9LMVBWdWtjVHBBcWZTUGtuV3NHNVJiZkNPQS1DQXJmZDRsNWQxZ0RrZzdPQzVrN055NzdoWHFhMlF2anV2MHdJRmFKRW5rRFdVUFpJU3VpbDM4dTl2ZkVVT2dxTlhmWTd3ejdlOTZ3bXQ4RDJ5SG1TZG16VE9wZEs1VjBQU0dHUXFnTkp5WEREMjIyQkg1cUVNLU5JbHhQcUY2Y0lhOWd2dzZVRVVJVDJlYXVVNkJxN2Rud3h3Y3lHNGxRTTFlYTZ4T19USkt6Y1lySzdvREtBamQzT3RiOTNPUGR5bHc1TV93NzNfSzVlSnRXUEhXeUhfbXV6aVAtdldCd1RLdWtnaFI1c005aHJIT1lOOFJFQmVtTTFWSmZidkgzT1Q4ZmFEZDgydC1RY2ZFLUtLNUxZdURCaGJ0UlItNlBxVDZYQkpTM3h1bHZ3RHBRUVU0SGc3U0xPTTd1MlQ5UGthX2ZURnpZSjlsZ1MtOFhidkR1VS0xMnM3bUVJOTR5dVZoaG1jaUpFcUJOV1RmQlRpUWlTbVNmVDlzeTJQRGk3SzRPMW5hNmRidjl5V2duTHFNWG0zWHFoaVVYTzFfRWUzdF85eTlSX0lSV2huV1RqNlRyMkp4a0FhMjVzVDk2MUU4cGMzcWVwM3dhM1V6SnYxN2FsTkl0UEg3bWJidlZUX0F4UUdLcW1JNjZ0MVk0RG0wZzkwRFI5SlN0aHFUT2V1VWpBQzR6V2xpVjM4dmxYN01BUHlKT09tNDdQaGlzQXljcHBEd0IzR21NRWxwV3IzWC1zWndPN2JPdnRkRW96bjZSTFZKZDBhWjh5X1JMeFdWTjY4bzdCaDhZdlN2bWszV3FQcHFOc0JYYnlZeHVCTzhkX3o0bnQzSk1iRDdNUXp4Sk5JZ1otMXlkZVBSalBpSFVGdnhmTWpvWlVyMnh6X3NvczZzZXNDeU5WeW8zYk5EalBsRFpYcHAtQmxmYkJTV3JJS3diUDBSODBFbC0xYWdOdDFHWEZLRHM3LVZadnNKYVM3Q3h6Q2FUU0pRbUY3TXVfYUY2UE1xVHlJWnRyUldWRHltX1pxWWpQZEcxZEZTVkEwbDRsS0JCNEEzS3FwUHM1WGFyYTFYUk5NVnFVY2ljWGUxbXB1SEtJbXRMbENYYy1nQUxVODVES1dEaEFxc1pUWk9uYkNxU1M1eEk0SmFiSzZfYlpfN200VVNVUUtwUmd6RUJDV0ctRGR3MlY0NXNQVW9Jd2UwQzA4RFBNWVE0MnRqMFg3a3VmUF9LckxwaW5WbVU2VDRxbW91MkhPLUJpMGhjdmJiMjNmaVBfYXVfQzdQM2FSdFQ0ZXZJVU52eFJyWUJZaTE1MGtka2d0d1dPdnhiUGlXcjZaZ3JoRE05ekdoVWZiR1h0c3pxdEFqcEw0LUVRTWhPMG5pVkJia3dLSjBNbW9Xc3BnSlpoN2YyUGRCYTREejJDS2paSFJqZ0FTMXJpcVIzMURqLXdTaGhnUVNQeDRDX3hZakNsMXRoM01qLWd3ck9ZUDJaT0ZVSnBHbWxWN1VvbnRDN1huc2FlVVg5SnpRdGhoVVBvWUxxZHM4WE92SFN6LWVKcnRpY0Exb2JQSDBvak5MaFNiSVo4cWNydWpWOUF2eU51WjVCZkEzZjdDWHhHY1ZHLTM4Ulc4eEFGdlFiSHBBZGlDNWtwa0lqbE5WQVNWWmplMzZwdmdwTVozLThuOHBKQUNST1l5YVdmUFJZT1RScEFCUk5kdTFpV0ljVUx4UkZrblVuY0YtcnBGNDdwMmt4U0tvVW1hN2VvOWJZdUhKRWd1M0dOaFFvVU4yWWh4Zmo0VGpENGxXTmVjbDVPNDdFT0tDZTIyb1AwSmdLYm54Qk5sTWx3cGp3aDk5Rm1SNFVocDJCME50NElfcjhRRFBiOE9NZTE5VVRGWWdxSEZJUV9OaWNOcGJLbFBqZU9FMVlvY3lCZ3VmTERwWGx0V0h1WU5ZOXl2S200TE84a013U2NXenBlck5lWE9jX3FNaDh6el9reGpvUFAtNkZpekVIM05jUVdrR0xOekZSWlRaeWptdDktNWZjSUp6ZEtaMnF3TlhIQ2VUdllpUkZTanMwSnQ3a0FfRmRZZU1oM2M1NlVJSTlwUGtMc0F0R1g2ZjBKZ1RkYkplOEJSX1ZTQnRCNHpTcXJWaFhJR0pkYmhsVzg0ckxXMUhVTy13SF96V0t4Mlg1ZVpyakY4Qnh5eFA0TmY3eW92S0JUalVKeW5yUTFTSEdEb1llRE1ldXJ1bmQyVzhRdThoQ1owemU4TUpHaHQxbVJSSE1qcy1rMWd5NzlWVVEycGVUMXV4ejlEREtSU0F3eG5sU1JMOHdmdk9sbUlMOXZjVXNWS2FXMEJaWHJGVy1ONHpvNUw2TjI2SkNEYUNJTk5xUDFNUWREQ1VGdUtwNGF2MmVQWncyQ1Nnc2o4MURVSjlfVHljWDl4YjA4dU5hRExNd01DMHozM3ljNlF2QnFCWWxjWHhiMU5oeDlyRjdzMUtUUTNaZGhtcDN6bURKVkIyMVc3M1BiUVJUaldqSUxISVprVEhGQXg0amNjeUdmbmNFZTBuTlBYby1WOGU4bFBVV1BhZzA4c0VlOWRaTms1UnB2Z0phdTNYZnpuZXJVa3dKeUwtS3poMk9fSEVIMHo4a0I4Q1NtY09BVDh2UmpicUpnOXd0cnJ5TE94Z2hnRU1DMmtXNXBsTktnN0Q0QkxZQk5JTm1GREF6U3ZiOUxBemh2X0lpQXM3Ui1Ca1hhWXdnX19wMHJiUFB6WFVxTjVLbWZPSHlCTTQxRVB3WWtEMzJZeERGWEx3bEdkOGdrbXU0OGZsSHhqOUJ3ZmluUTZxZlNGbksxMXlxMVRTQzE4d00ta1FjNFplQVRPdEtnRlY0SGJjdzVrS28takc3R3llY0JEejZSbURpWUtCRDBMSHN2Z2Q1VkV0b0k3anRvZENDM3AxUm1CbDlOanRzVkt5LXVsUU9yb1RKNm1BUTlmZjRsNTdEZFBiQVJ5cGZ2X0stSFVSaG1KcUE2cW0tNGNlLU84MDVGQWNieFNGdzM3MnRjWDZ3bVpmTWN2RVExSDB1V3BsdHFrLUJaa0Q0aFJ5THVwNDdWYUotYXJ5czg0dXlQMmNfdjRZMTNBOWpGWHVpeEJCREFFSk84QzlTS1pYZjdMV1lOT2MwQURZc0tqbkZTMmZNNXZCTmlsa3hab3VHMUFSQjhUdFRiaXR6MUdObHhVR004NzZwQ0pQTUhpYXI5YjQ1aVk3XzAzYmFHSTNwOTVnMU5GY3gtdThiSHk2cHU4NjB6bTVmOVBqTmJzQjBXaGVsbFh5TlZjbGdWTU1nVTVMZGxIRWVQd2FvTVJPY1YtbktNWXVCWVVvNHBzYzhrdHVsRXltT3JjSDg1NkFZcFFuRWZ1SFZ3Rzc1Q1U0OG9Fam1JNTNPemJGcUJrY0pyT0FqZ0ZZT3ViYXg2Mjh6TzRYeE1JeVdWdnY0TmJqbXZ6ak1NWkhhUUhoOWdDNXh3ZGJoUDUwQklsbGNGaHZzZUFidU9tNmQ2ZzVvamRvTnNtTU1EMTFpSXpaZU80d2VubHk0XzdCODBfdE94cFFXYUVUYTJOUU43b0RlTVczRlJSdTFvYi1JVWFCNi1zMjF3Qy03X3g4SFEzZlhGbWpuNlRKSVVoODhSaW91OFhRaGxPd3pUNzR3dXhMdmNzU3RMdzRyZTJaUGJGNG5nSUZIN1F3aUFqUWwtaUF5cTI2T1RWeURrSElNdzcxaHBwdnU4bGxqTXVzMnd2VDRfUUI1Tm1yUWF3R05DaFc4Y2JWQjBKbmF3bndBZk1EVWRCQnR3YTJVZTBCQm4wRkdKUWhDT25tQnJNZlg4bVpZdWROTExFaVlOTDB6ekdkNUNjTGdZV2RmM1drYkFmLXJhYy1Va09mNVJWdVM4dVRaeEtOLVVsSFhsTjdfaksyRTM1a0pHSWFjeVBQM2k3cTYwdE5uQmgwTUpyM05McDVoMnVydlVxWEJOZzVPaXhYNWU1SzVxV1FzTHdTbklOUDVZTFVfTk50bzRlU1ZhcVJtM1M5dEY2V1ZROHVDeUs2enM0U0ptRUY3RmM4RVJ4MmhFVFB6SDNyMmFKTW9aTmUteXhLMFVxM2RlS1NZUnJ3cG5MS2NYak1wTnlWOUVCeEJPLU55eGpDOHkweU45MFRBdlRzUEQxQjBfVkhibkFVWEFXeERwNFVLZFpaNmxLbFlwaE1nelk2NjN5MDgzRVM3UTQ2bEFEN0tHQ0NRclNrLUNHb0tRZ3hYQklfX3hia0ozeFVmemFsRHlLQzZzNmlDVThEOGtBbUxIcHB1bjk5R1lHZTk1UWtUX3dMQlhDVU9yM0dxVzRnaGRVQjNJNnp5b2FJVlBxWC1xbjY1WlhtLWhGODJKYUpYaHA2Q203ZnFGUVVmcl9XQzc2a3RrcDZ2X1ZITFRfMGNERnRXcUhFZnNmSU1yNk9rYTJNUVRNM0RGTjV4dDRNMjBlVnNQd3pBcVduRnAxSVItWm5TRGVYWDh6dkdJckpqRVUyeVpsNzJFRWhQWVNSZDhtaVVPLWt5c3JFcVlJN3ZlbVJCa1ZoNWZsM042S0RiTFJtOFVjZWhHU0tWZmp1Q0phc2dJYWZjd0hCNkd2S0FwWGhLN1hqVExad290SXR1QjlwVGFvNHVtcUdiUlRqNzR2R1c2eU44aDRlZHlweHV2WEh4ckpVZGdFWmU4TGdTZ1BfbmRKcEIzVHhiV19pNk9FYkhWWVUzdW12SHhuWkE0YncxWUVUYmRiMnlKQkdlUzZvcUhWT09MMnhpa1MzVmZWcVQ3ZkdoTWNFcWF1bTJJaWNZd0VyTUstTXZya19rYU5kUklkVndhTFo1N0NCWnI5RTY3TnFTQVMxUnB6NnFseVdhaWhVWmpBQjRmWjdlQ0dmUGRhTVZJTlVTNVRnYlptb2JPOFJNSE95c0FsaWsyLU1oc1RVd3VPV0RwYTVRZjdiclhrTTFnc2hpMV9WRG5fM1JKMllWTi1yZTA5ZFlwSGREUHJlS1VnQzdUUlIyUmVwREp4UV9DdzdPNEctenZ5LVRTbU5tenE0cHNkamNJaFBWaDZhN3l2eDc1MnV6MlJIcjhJR1FRRlVWVTVIaVpuaVNHdlZNZVpxWUpjQWRNczRLX3hJTlZxTWlCc0o1cjNPbUh2YXNnMHA1d28yRnFBOEtVajRRalVKd0RacFk2ZTREZVV3LWNkTFBpMHo1OGhnS0hFVlBDQXRfRjNtQ2MzZnVMdFl3dHA5b2I4QW9WQ3lHQkwxWlFsMlpLVDF1VVhqSDQ4TXRvOXFqVXM4YWhidEhyRm9RemNaVm1icUtCZDJGTHRJSjAwbnN2eDdjMXVZU0drVV9NdzZwNXhaUklnTnFNNHRMT3F1OHRjbkFzbnVDd1pfSkxLZVFFQmtGdnNjOUFWVEt1bnpKOFFQc1E2SEtHcDZwSEJYeE9OUlhyQlVLaFI2THZhaV9jQlhzd1M5U0s3VlZQQ1JsNEFGclF1WExxSzE4Vk1aVG8xQWlrYU1ESVUwR3JhdTN1ZW1ONklRQy1LQ3pFWTBOMktuZXYwbU5yRHdXYjhnU3RNU1J0azF4bXM2emdzRVBTWVVDd0ZRc3dxMHlEcC13R2Z2UVJDcUNERjhMZTVEd21BUlNqeXpqbW1ZWEE2YXRMeXhNR3JOSDYtVGVJLS1YTTlUQkdHOWdteUtFRmxLZWQydDhqUVRuZ0VPM2xnWVB2ZVp4OHRjeDhLWmNvUFdwVjJxOEFxMXZXUkhpcHBYN29BekJaOExsbnVfUWIxVjdUR0Z0ZTE2aHFNajVTenNPeVJMOHBIRXlkYXdnMWJ4MmNCU2Y0c01tSkJCd1cwTHZ5OU01TVE5SW9qbWEzRG8xSzJsT2lEdUJYN3QwNXlXVmRFbTU1Yk1JeWdTYzdhMF9uT0hyWnl2OXFEYUFxbGRaQ0p0LURtNUR4QzFWS2V2STI3N3NjZnhwREFMZEJYWmV2VDN2N1V0ZkM1bG9hdVo5T1JsZjZ1SjNOOElnVC16X1FRNkw2Zkk3dFNwUlB6LWFpY0FwYl9MSVV6Y0tZYUhLZURRRXMxWUkzc1p4aFJOZngyeGwzSXZ5cVI5dTdoSUt1ODVRMlk4a0JuVHVzMmlmZXdudlFTUHY2ZkxON3djZFpWOEUwREZrdW9icmMyT0MwcWFTNkxseWwzcXZoRnBrVlRUd1RiemJiN3pjTVpwZkUtaHV2NDVZN2hHMG96aEpUVW90R01PY0ZaZHphem5hUFJIZEJock5DbnlnSUNNNE5xZnVKdmQ0QzdoaUNpQTlRVThSMGpmNFBpTWxEVnFsa1BOc2JXZW1JdEFvTU9Mem9kRjhjcWF4SWUtLXpOTDhFTDJhQnFrUlVBNFdvaDhoMXFWalBTQjFSRGV6eENhZ0RmX3dGR3lxWlpHRlZ0VWZ1X2tGSWRUelFnYzY4NWw0T3lLajJFZkY2M2YyN0ZGOXZvSnE0Y3poLThuWE5VRGlDS3dfVEliSktjQ0NnbG5CRWRYSG5IcmY2ODMxZjRUTXZNUFJnM2FScXRJbjVJZGpTdWIwTzJRemVwRzUwNnJhM003T1h2NExKVTNXYTVCVkZublJWVkttdW1rb1NtanltYUU4ek9aZ0ZFX21yNEdfbjhvNHBrY0lVbXRQdmMxaFloOGFTRnNjWlA1Zm9DUkFEYndqblQ0TzU4SUlrUXU0bHlyZFVmMlFONkllUjlYWHVmVXloZkZIVkkzbUZ1SVdORGI5THdMWDBqempYRHZtVWdXcHA5LXlmNEVYbW9KcnJqdUh2X1ZtZTJtX1ZGdXdZalZUOEhoN1pPcjFrN3pwaUpvYUtHVHRHY1J2ekFaOTFGVE15VVRseTFnM25YeXZQamlOTjV0SDVrQ3Z3OUNfTnprcTZBYTRwM1AwY20zdC1KYzZ4eXkyY2pFeGFLaTVENzlnbEdVcmxFVG5Eb1FzMEVCTFRwUDFqMVBsM2VMSHZLT0dGVXdiNnNjZGUxQUE0elluVTRKSjgyTUxXamhSYi1wWmhwT1A4OFNCRWpBbUs2NERLM0JoWU5UQUdrb3M0c3pLb1NMd3RwSWlHRU9maVExcUlnWllDN2dZR2o1WWpoaFlHTHUwUGVKNGJjS2NKcjdqamxTZlZfb3lRRG8yWTFPbHBsWDRrNnJuc2Q5VmRXOUNVZGF3clVDaVo1THFvTnczeTd3T01nVDBEX00tQU4xT2Z5VlNyZXdPdTMwZ2ozWEk0Y0hWZUJKdlZzUnZuRVZ3Y0FrZW1KNVBHNmt2WHctN3diZEZBb3BKOGc1NkhkTGpRQW9MUHFZeG9zMDFHUzIzLWxGeHNTbnFqdkNLTVFnTzRDc2pNN2VCRWtZZ0xuaWRuY29sOUFxZGc3aHZ1UkI2aXRzLkZVQzZzRHhHRU5TWFBnRVNhWlNYTWc"}' + string: '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmFvY0lxQ1Z1WFpwOTZLSTZQdmVlbENFZlMzeVl4ZC1yRm5rZkNTcmdXUVlwU2tzUGVpTmJENFZuVEdEbGR6dE1GRlZ5SGdMRHVwb2VJWDlIU3ExTEQ3MVFSNWpYQUE2aUZpendrLTYxbkJ5ZFY1SEVLcHFpdjJwckp0T0FRTlQwZmpsWlJadzBiSG9zSldIenF6SDZXZktQT25pOS1GTm1TV0xvZFF1cE84WGU2bC1PN1RoWVlhc3hoLXJNN3M4QUJrekZEUkVPUm5rTnczMVJ6SDAyQjJhOGk2R3VfcUVtbTdvUDdOTnJRaTJfRGJ6YnZBOFROdHBwU1gwbnc1c2ZnaWVFWmUxTmZwVXNSZDcxMmd4YWxWNUpuWklJaDAxcnF4ZUVZaVRRNlhxdXhPbkZJd1RaTTV6WTZSSVJ4QW5rTk8zb0pMUjlkSTU1cG02ZVlxMnFoZy5DUnVlaDRPYncwWGVkMXY3TU9GSVNnLlZRVXdIMno3UWV3SXJZN0kwSXhEU2h6Y0FLQmlKdThBVFVTQWZvUTBfd3J4WEZBNkxyOGlhM0k3clFMYm15X1hLbjNwa3FFR3VnVHZlTm9BenVmaDJNR1pHV1ZtOUJibGhCV0Z1OTZUS0s4cVRqOHJHUHpaZHB0MHpsSlloOTc1S3hZSnljNkdpNExzMVVqVDAzaExEcXVpYm9SMkZpNk9oZFRQQ2p5MXd3OXlNLUhKREswUHdzSUw1VW41YXFGaTMxUW52VlVja20tZUVNRFlJRWwyeTNtang0SmtVS2Rtc3FudzYtckRRcmd1OHFxV09NNXpDaFVKRW82dkQ1bnVCLWNLa2hJS0JPOEItM3RVbnQ0bjBQWldGSzhDaW0yb0dyV1JNZWtjSGxYdFp2Z2M0OHhVRVdQdjBhQ1l5QlhzOTlac29DN2ZRd3JIUllYdzZXNTFycUJqcTA4ckt2YWhTaVBxb2dkMmNfLTB0VVlOQ1BUWnBrcjVzanA2TTVvZUZlTG1KOURlUnVES3I3bWg0eTc5UndWam9xZ1JGdlptbmVBcmY3Wi1USmZydmlLZ3lLUjlUdjN5ZFVjNzI3aGJMTGQ5NC1LNFduMU41NE9paUtRNmJEbjRoWTd6a283aTdHV2s3NmdXWlRrRGFlVTFtaDgwQUE0UGEySXNsNXMtaXNuZkYwVTFXNXh6WUZaWWVYUnlNSDZkcUpHYm81RDBnNzZhdHdvTjg4eEZlNXNDVEpYQjBnQTBRUUNRNWRlX0E4WS1rcGFWakpKNDJPQ1NEX3RkWWR4a3JDOVZwZ1FlM0Q3ZzA3QmdqVlU4aHdFZTVkTzR2QmlRekdxcGtxX1lqWkRzSTcwWkFWYlk2UTE4QjRoWnJFd3VUZEs0QVdfS3Q4YmZiYVVuQlE3d2Z4cnJ0SldZeko4eUJaaFNOV201MThfekRoMmdYLTBuNjViMm5rUlVFckszWXBEVEpMZ0RZMnk5dGtETkdHX1dJUURIWkNiV3dDRDBTbDBheHdybWxHYmYxWS1heEhudHQtd214ODROZ09DcllEZU1SUnVkbjhScWlKSXF6d3I5N1JEU1pQMGx6dVZ5Y2xMX2RRSmdTYmtrRjVTdWdZbHotOFkyN2wybWV6VldrTDgxZ2w5enVMb0RiNmFMalA5WFBqdjV0WjZsOXF1azZCcjF0X3pBTVpadzNjUGZXOUVXa2hOdGNNbEZITXBYMlFkUTd5enVQSzJEb3V3bzVWV3gzQUFTelJCb3FJQmhIZEZLUWgzNUVFQlhHcEprcG9USzktTWhINkw4Tmd1dl9MVHJQNnBlOXBvS2otTjVha1VtUWtDY3ViTGRSUjQxcnFUeXNoYUl5WHBEYnVQZ19pbUF1eWpTLWR4VlZwb0Z1bXIyMk1aMFgyRTN5R0piZGg4cjVBTk1mM2lwZUNDdThOeF9VWUxubGFqczlMR3M2bVBneEktUFY4ZHBoUUxwa2dfM1FYR2xLOHcwcTVJMU1uMFNlc1R5NGJiMG12N0ZDdUYySzFKYXR4SGJqY0FFcmlpbm9QTk1yYUt4X29Kb1k0cVc0R09rYUFkQjA3MTBnZUJuYnBXcENuOGJNWkJ4RUZHNE1XU1lfbmV0cHFDVUhUZlQ2S2QwRzU4LUZQUHpxY05naXNGMUVuY1VhU0IxTVJ0YXVPMV9rSHgzT0RPNEhfSm5nYnd2bFRENWdXcjRpU1VYYldSdWQ0ak9YNXhJYkY0NzVyeGUxa2RNc1pORWhkZTVHbU1aQm9lSUd5VEM5bDJpTXNMQzFmdXp3YjU2SVczVlRmTzlTazVqOUN1RUdsMlVGcGd3N011U0laQ1VhSWZId3V2bEpXUXp4QlkwaG9xdEtSSjRQT1dWU2FhRWJ5OUdMa1VUSmFEV0p0YWFfc2x2UkVnb1E1WFFWeC1oUHZaWFczWDJlc1JsanJtQXZnVGJFWDQ5NUpJbUNsZTByaU1adjlETE9OS0k0a2NzUU12cGNsaUpQWHhsUE11X1dfZ2RBejExdHM2RUNGY2lVdHJOU1RMU19Qb0dqSlRSa2lEcERYSFBFSWpoaVp2X2xES0NUMjF4b0VsUE9kekk3OTYtNGpaYUUtbFBxOFpmTjdzQ19iRUxtdW92TjZIOS1yQ2oyc3BuczhLSDZzQXliZDBFNmxjTU42UHlCRWJWazU1R3V3UWNrLUNjaDA0M0l2OTJVVmZsNmJROUJsc1ZjenFoMlVGV3Y4SC1qaHVFeF9XNHZidFhHRVdsSDVSRDdEWlUwRmNCbkprQW5senF0d0tnMy00UGg1bHhTSWppWVJpdTRKZzg5clRuLXZIZUZnc0hBWF91TDFjM2xSZkd6MmVtQ202Z0RrNVVKdHZSWGtmUHREUGVHckp0SVlTRExqWjduaTlpRXl5X1JFVDR0T3NEX0R2N3hieXZ5SHFHUzZOa0JLRUkwY0g4OWpTVllVaGRxZk9hMUhqUU42OW8wRmt3OHE1TG51c2NXSW40UFVyajBYOGd5c3RIMWVha1ViSU9mRm05RWtzdzh0a1Z3S1g2LVlMbHktUS1mTlpwVXF1Mm9HbkxOcmV1dmhoWkJHdnJzQ0pzUVF0NHRjTWNwakc1dmVRd2RQT2l2R0tRUmVCOE9MazJCR2RlZGZXeVlyRGk2d2ZUclJ4VjZyb2tWUlpvVk85dTd6ZjkxWl9fMHZaRlFSN2ZubE5DVHpEM1dpOWdUUEZJaThFekdfS3BiRHNmRkRNN1lCeGx6SWNkeDdLZW9aZXhHRXpUdDR6T2JfTUllWTFGWkpFbDdJRUxxUWJydXhFMjBURVYyYi1VaWJvWWpvU3hRVUNZLTg4TmJ2czZCWWF4NGs2eU9yU3ByVVg1OXNNcVFPd25uMWRZUlFaeHZ3WGh0U3pWS2d2OGtzYkNvd0RHRW5pb0cxaUJKa2NDR2tlbUxPRzlMb2xpOWxRVDJVRlpBR0VzZXp1S0h3endPOGprRkEzODk5V1JXWHBqVFRuaEN6Wkdqd2JRN3BESUVBWFhyakIxZmJ1LUZhU2VYUGVhS2VkZDdMUy12LThJaTJFaXVLcUdIaWttV3MzaVJQNTNTN0VkNlhtMHJydHpSVE10RlJ5LUdQOXlWWlNiR3JyN2lFZ2YtMFhLT3ZPZ1FrQlo5enFua1FIM09rZVlfRHlNQVJqSEdkbTRIYnQ3d0VrZjM3MHJ5M2JVNTlud1ljWEI1b1l6YUQ5STVxM0Zmc09yTWhlN3hIVDZxZWRqbXlYRHNqdi1DQmJVYUVpc1lPTmFUUW5TbF8tRjhBZU4yajJrYmtCSl9tYzBSa1BncHA3cjZJQVRCRHg5dkd0ZjZkcTRpcndybWY4MV9SM1dfQ2ZhMmZlZWtzUDBFVjB0THVUVGloM0lEUlR0eU80MjVEQ0pCSm5BVGNkc0tPOVhuTDl2NGlMbG1Bd1E2S2U2Y1N0cTNTelNNUXhLUDI4TnlXS2lKV3NLemlMZ3ZGV21zQWQ3TG5Gd1dzWUFRa1ZidjR1WlZVYXhIaWJ4OUEzZGhsZFV5cHBEaUxlcWswck12U0JnTHNZVWk5SXJHbkdfSDdISzY5bU5CS2lNel9abDdBMjBLbFlJdlRVaUFBbnFLQi1kdnlCdmNVN3l6bzkxY2l4dUVidnNtYkNFUy16WVo0MUNLWGoyZ3BTZEdlUDRBVHhOYnV1eDdZWkFPUnlFamdHYmlJS3dUN1ViU0ROcEZEeml1eHBVby1jZmxabjlZZTNNcHFlLUI2NXF1RElxcDNvelJvdGt2SzVCcUpXMkRnTEVvUjBKZTNxb0gxVVJTT3Q5MlY3bmttSWRrUWloMURuUkttSEczQUUxS2YxUS1fRFJGSmJxTkozRklFYl9ONVlMT2hGaldmN2p5ZG92WEVqUnhvYWl3ODJOR0tHZTBOZDRhNW5JanhHbWhuRkpVVkxEajhnZHNyNG9ueTR3RWtsVnVqdG1DU1prb2VSRm9tQmtfNjlEY0pFbVl3U2hlY1JJdktxeWJ5MWx2VlNWTTZuSlpPam9FSEE1clNBVGxHQ2VxbEdWaVB1YVZGdXhmOGs3S0FwcGVFMzI1aVJXVzRhU1FHeE5NVjNWWHpIa2dZRjg3UldnLWNWNDI5M0ptSC1IOTlmT09Ic3JiVW9oaFRIS0E0MFMzV3kyTWc2anllQ1Jldko0SXhYVWVZclJVcVhBWnVOalgyM2pZcExKYTk2bkoyZEwzcHRiMW1WNF9vWnM1eG5pRjhmdEFEcTZfTDZFV3dRdHdfS0pDVkE3a3dGZHBOaFM4YU56V1g3d0ltNndRQ3lsTC1jTHQzSm9EX0ZtWmhfeEhmOFludFRUUFpwaVZyc0dpRlFKRE1YSk9OQ0FEekFlbk52cFVUMTIwaEtiX3BEUjdDa2RLbThQb0t6ZWU1UzhaN0h0NTZjYWltOF9ZSXZJLURZdWFuOTJ0ZXgySk9Ca0paV2p1Mk1LZHVOUDd3UzQ1dWZhTHZyT3dKSlRRQTk3eHNnVERXejN2WGJmSTVCX2VHMzVJVG9RX3EydXJOMlBfb2NlN2FRVU1mTkpzcHNQeTBkckVfekFoUW9QclVPNk1uU0dlS3NrRVAybHJzU2dhNUpsTUZyclVNNVdtMUREcW9oWnFqTDZPeXlPVFVvUXRrQ0dQUlVTM1lmbVFJcVpQV01NSG1wbXlwZUdiX0xSVDJXQm1hSFFOUVhfdVNWVG1LUWIxbDBJa1R0eEFNQmQ2NzNTQUJNVVllQ01zUWlXRWdxT1dBbVk3MjZWeWhneFg1bjMycEV5d2x0cXFGLUd2djlUMm82blFQRnVrWmNSc3c3TGROREx4V2hkMHVyTHFneDVyQkh4QXV4NlllaE9QYV9sYVR6WUVwUFhGSm5yR1c3LXpsYkU1UmtUUUFRNm8zbnh3TWkyLWFPREthSmxWS2EzNW5ZTHZPWUZtSUg1b3gwcHVBOFZGUXNmVGdGSjZQa2U5cWxic0JwcW82S3F3di16czIyWkxhWVkyNzlhRkUySXZ2RXhHakk2cW1HV2Y4RTVPbjFwOGZWSndqQXlqc0gzLXlwelFJOWFMYzF5WHVWcXFvNzVSbGlHZGE2Z21Ec2ZSRW5wVVNrdUgtYzVXTFBsX25jdFlmY1pKUU1IbWlLNUpPT3pnTk1IOC12ek1FY214NXJQeHpuTHI2cV9tWjhKb3MzSkFiMXliYm1PNmFoR0IwQ0hqZ0ppY0FOdXRQME1SMXd2eVd5Q3ZVUzFpclVnQ1RyLWNYOGFGblNKY2REdUxhMWY5TVV3ZmJzZTV6YkpTcFRpNUplckc5Sl8wLUwwajYzc0Q4ZkxyU2l2MGRiVnlhSl9SMTg3UWRWQzRMRERWMzhhRFZNM3FOdmNwYm5MdzBtYlZzZ2NFX1czTmtlWTRsbDBLa0I5T3BmRmdNX294d1p6T0R1S0lsdHZ2YVhkbGZha2E4a1BDdXJxS1BJZGxydC12bHNLdkdpQnBjNXVTUG12aGcyUEV4YThCQ1BBZHdocXo4aDl4Y2YwVko1MWtPRG44dWFWOVlzUlhtY0pOdzhZTXZXQXJnOXU2RnJzaWtsRXQ2NUdZbnZETTN5V3V3WlV1UUw5LUVvUUpCdTM2bUY3amwtQUFrYTRhUEZONWtlOVdlMEtObEtRZDFOVG9KR2w1dmZBUV9UQ0FXQUtQZjlGSjRyOEpzVFpWWW9lZE53OUJzQ2RLX3gyQ3pTdkZubzgxbE1JT2pyYWZLT0NpZ012QTBoTkM3NE00MXo5alZ5S1d0T0JXdVRWNEFFbGtDS1FDUXVBTnVmRFIxWUNoX0lsRzhVcklFR0VIdTdGMzhHUWVvWmtCd1NNMHoxZlEtZVVPUi1GX205ZHN4bHYzNGh6TFhUckx2ZGJ6YzJuYkV5R1pnSGhBWGVIZkx3eHFNWURwY0RmSW1XcEwwekJlT3JWWkNjZjdwcmNON2FRUGxSbTVYUUNnLWlVOUY1VXdkMUVQd1pFUGlNTGxGX1k5cGg2N0lzemNReG5VTmZZYzg1bnhIWVRBUzQxakFicmdJSjdfSy1KMDR5MVBib1lDb2dRcHN3cGlyNGZNdjR2c2RuMUtVbERxdkh4UjdZTkNXU1l4dlFmek4tNzUzazQwRERYd0xlXy1yS0tzSUo1LTY1RTJXYzZkNXZYLXpzdmJTbmhDRnlidE42X2FIZzZzeHlPc3lqTnh3N1pQVER4QXRXeHlZOWpZMzNnamI3eEw1WmdSMFlKRGVfaGpNS1FpUWk4LU02VHBlOS1YY3FuakVuWnBFdVJjUVBldnZKdjF6LVQ1YkpLMDZ0VnRLc01wSU9vR3FnbXBYNktzYWxNTXg4QVdCWUoxc2VfNmRZQ2o2TVVuckpIeEUwSXhzR3pOWkJlOXlVS1dzU2h5bUhZWGRTM1RDekxfNXhycjVuVy1nbUt3MkJTZUViNm52NGFoNTh6cjZ2MVBFZ2tMTGRuaFdPbjZZQWZCMzRzMDhWMDFiUnhUNEdSNEhSOWExSVJERVFVY3c2b21POWdoeDZCTkJKN29sNnVlZnBZbzYxNmdFMXgwWEFjQlBrVUJqTmw4eTVockJsOVlGMW1MWTJ1bEZUQkctaVlOdHhIdlZOZXllYzY2NTdxWlpYcUM5WlFfQ0txT09Qc1hrdWtEdndxMjhyNXdyWjAtbXhpOWZ2Z0J1NElsZ3FpbDIwQVZtSlZJRno4c0E3cFF1VThYeVlKTHdRbllTbG1mU1NqZEtoMUtrWnlsN2pwVjJ6ZlBBV2I1RWxsSkM5QmZUXzViQ2xfbTdpWUFlRGlnTkJXZXNwaVhuLXBUOWNnc1djdUZoYm5BTlB6SGtOYzZmOWVlb3p1OUk5N194SVpKSjF6eENtQUtZck0zTTFCNE1sb1lpcWJfblJDWGExbW4wdXFBVzBqSDZ4RFFLWW5tQVQyYnpRYXdYQVhxSmd0TXlza2xVaU04MkpOR01LWGFDLUZPTEhvYWt0NENBRm9CMzg1OTJHZ1NxODV2TXRqX0d1NHVYRmRtMGFTeUZjQThSTzlVTTZPelhncUNkaTVRWjZLMlVMc0FQTEdWdDFFUmZsWlFuX19SeUhfanFfbXZKTzhHcS0wWGd1RzZkN0pLQTluSE9QaVRnUW5yeWV0OTJqTEgtVTZuMjlIZkhrUnFNQWp6eGlCTk9WQk1namd4Y2VGeEVXRVFCYk9EQk5PcFJxM29oaXZ0WlFaMFN3ajI0eWloREQ3UzRTOVg4cXhhTVBGNFhOS0M5TW1YVzkzSGhLd01ZYXJfcTZ0UERxTlJJVGV1VGZCWnN1bWJITGR1YVZnWlVLczJTQ1dEQ1lrSXpHSHVyTmY5TFNtYWppOGJhZXVBal9PTTZ3TnIzUkFTdHhadlM0bGl3RmRreFBvNFB1NE9qbDFDTDFqY05BbTlsZXBLNlJxX1QtVkREeWdkWUxsSmM4U1UtZ3JCVUFiRzJtaGpPTXF4dUd5emdXNnExLTVjelBDeUZ3TGhkaVB2MHh2TGtyNm1Id3hMbWhldVBiUzNsNVlaMENQUl9qNTVNVnFwcGFzZW5Qc2JFYnpjbWlaQlhCNF9uV3Q3bWlSUFdnbWNCcFp6MUpMNFI0N3ZyX2pIanN6SkRfbmJhRHh3c2RPakFEV0lGSTlhTUhpM3lFdUpwMzgwRlFrWmptUndwaHoxSTJfNDhXaHhqLWlJcjc1WXFlUjVaRGRDNExNUTRDU2R1cVREV1Z3dm1wY21BWXRzalJaTlFESHZlTjdYbnJBU0s2b2pBN0dvOW4ydThoWGNyS1U0Sm5uc0dod1ZRSnJlN085SDJsWG5XaGZraF9IUnB3QUJQd2xZQmJRUUFzb19YUkFaV1AzOS1oODhNUlRiaE1jaXVHdl9Yb0c3LUpkSmpWYnAtT0c1cUo2dFAtM1Z6OEtSZFdJT2Z2enUyZ3YxSmJfeEp6LXBDY2xxWjlDT1ZNRlhCNXEwRHVTTGR5S1hpa0xVZll0M1hyM0Z1N1RyeVpzMDlrR3drV09NRmFCTnJXTHlKNkJIQW1TUURjZUc2cmNhTmtRZGlCOVItNVJKWXpEWWs3SFQzdDFvZHlEUDZBdDdfVzBxcXlsak80ZlFiRzJmM1RBMjQ4bHE0dE14c0R3Q240UmRtOGowU3Uwd2lQdzRJNnlvRkZaSUh3bjJhcVljSWJ1SllyZ2xJeXZ5a1cxSzBUdlg2ZVFId2k1YTZ3MTZZOFF3S085aEFJZlV6SWtxbl9ZRUo4dGp2ZW90UXMzWFZEaGtaMnI3UkZjdTBCc2xKVEo4TFVPLXhRNEdtSjZsVlN3OUw5TjRJNjhIMzRwUU9Ta1hValNaczFYRlhJbVlQZ3pRSEhwc0gyTTVOV3NsSmlucU1oLUxLd0xsQjBpaWF6b0lMRXVFWmJjNEpuakxCa2JoU2FuUEZZU0pNUXNRc21wYmxzUXVhZVFzQUNITUJIQ3pHQ3BicE83blBBbEFpU0xpc1hlOUs1TWZScHVLVDFDQTM2Q1Vpb0xEQ0haSW9ZMEVlVDJzQWthWW1tMjR2S183cjdWSHR3NklpTUxGdzlhMXJhdVdZSl8td0tjNERYM0pubXRWRDU2M2RwNm9ta1pMSm1Xd1hQOU9BaVFKd2g1bmFvMmFkX1JQbUdTbFZpa0x5OVk3U0NKbjlUTE9jV1k0R2hxVGZzTEE2RkN0RkxLNDl0dzZCT2hZSEF3ZkJlWnNnb3cwV0hYeldENzRiUHYxWkV0Q1o0VnFoTFdzRnRaWFF2V2FMNmhLUTZaS2llcktCdTcxbnVxSE9UT2JIVXhxOGJ3Y2lDVnhXaTNJalZUZlJ0cUltSXQ3Zi1yc3BncTFzbm5QRDJGUHEwRmNrdHMyUEJIWkJMOTEwTEtKVnlsRE5uR1J5cG9zWmliRlNkVlVqeU5HYUpyVTNEZGZFTmJ2R1A1TjZFbi0yTzNqckUyWWJPY0EyNXp0eTVvblhJQXRpM2hzZlAzWE5NREFpUXY3Rno5cjZNcGdxUnUwdndUelJtSDl2ZmNJaDhRX3VPV3c2cTkxbWNWTHBzcHhSNnE3a3hZbmYzTEhTYjE4YXd2bEsyMGg0TzVFVTJNWlVkWHJNaHRIZ3hXRmFtTTNLeVh1bHM3Ym13NFhVN3ctYWxiRC1oQ2dkWTNLUjU2U3MtM3hQdUwyNUpPS29ERHdldVVJWkx2WnJwNnI5bnN3VmZIZ3h1bU1GcTFQVG0zbzl2ZmZyc0xxcmQySm9VZU5zc1lVY1R5N1QtNkZpOXgwdFB2MDlzdGkxT2JCYVVvNGRiWHRRczRTSEwzTE5aZE5yNm9SdUpUa0hHYnJtc3ctUzZ3cmtBdDE3Z2c3TmpHOU5fX09GYS1VTVlyM0tJWXdudnBHQW03YVQ5QlBkODBjbFQ1UTVUWDJPYTRNbTEtSzh2VEoyN01CUjllMjZMMUNfcnNXTE5rZE91a0ZsUHNaNXdCTU5kVXNUajhVc21JQ080c1ZQWVpnbGhEMEtZSUNyeUpXdHR4Z25TNWFzQkZPdjJWOWszWktFOFZ0VE9GckhFb0I2QTNUQy1OdE1YNHluNk9KT3Z0OEcxdE96dkU2aHdPRHV2QllFZTFtb1pDblI2OC01WFctaGRVXzRwOFFzMmtTaGgtWFh6dUdaalVJb1Z1R0NsdWQwZzdIUTZtcC1wTDhhb1ZKbnRHWjVlVHRRSDBGNmk0Q3BQVl9hMkthT3NFbzRtMVd3VjhOSVdodW1XMGR2OHpfOERrcndPTlJwUVFKQWZsRkp1enFaanRYZExLdEN3d0ZoZ3ZnMWwtVWYzYmJjc2FjODNFc0l2R2pMcW9rZVdFdzBBSHkzZkdyTGd1UlVtZGZ3MEtjQjFuaFdnLlIwTk5LazVCRlFwSXJnRUJCc0I4emc"}' headers: cache-control: - no-cache @@ -77,7 +130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -91,11 +144,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -113,12 +166,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/7cb699572d7443838ff94a8a0e79828c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMQcqtwA-nD8Gg7Px-5JnqIJUW0OIn2XFr2X4DSJrDXwzdOClqHf4miDhUE0swSJYGUsxBYfJ8in9sqC-1iqBP_dcKvGYaEhjzgEALe14tqLRSmLDj6C6rHUEUBx1nWxOZof5DEwnlDR0ywUscQgkmnfoSjWIO8w51PgxZ_NgovDfnXRZV9oDAKSfcmqguWSPE2uXCGYIdEAiLleBgfUj47W8gTzROKmaWu2hQ-e9xrQuN8KozB8j2LZfSnNvKI6-mN6_E_Son8OFtZnOv-NHYvMmGzanj5uT6Kdg3Nb0M8HQUKov7NC0_3MJGXkWjdOAjER6QZf981xx81ddHq3BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219221,"updated":1560219221,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/54248fddc148400a9df744075bbfe2e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lsgBFBFSUvohGWs21xzMCFc1IRDfnH0UlZ8aJnqVozFkc9rZgaBdr5IOVmt41hxbJY2MWuJxGpS4xAZ8AmrQZ1MOmS7nUiMmorcyqZZIcZYz3P7YXLjiv-ncb9rTty7IJ2R9v111Beiq3tNdHUrQYUt1wiysQPcPk69FGq7h4B5UqDM4SHQqXF-Rm3R2fU9wct7bx3F2XqYcaMmQWZBiYMH8vbooZWyh2QziCXVB2MhfBt0gTfoLUGT4hheDyLfIgbhhky6Hi0DC-3Ecll3tlK6JHAR6V1jLxJveWPgUy2WgRMSTATWdlcYL_i9Ji9OT4dSLkCFqXWhRN8krxXf9dw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -127,7 +180,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -141,18 +194,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLm4zcmZBbzBINl9NQW9lMHJDbm1zcXJRbkpLYVRlNVNjTndPSWF5NDZ2S1lXTDlZLXRjMFhkRklJdDlQNjlMYzVaNGNKM0dHRFVXYlhIbEVIa3ZLcVRyX0huWmN0QUZpNHB6QVRmQ2NqMG5RLU5WTG5sMzRicjBfWlQzVmlpWW5zUEJqZ0JiRmhqYUJLSXU2aXhhTXVyWjZ3eHg3NVdTSzZIc0hXZlRnUFNoZ2hLQmVUelR4ZnhRcFJUb0RDYmUzandOTXZoRHF6aDBTWlBWYlBmcEx0WEVDN1JZcFpncm10LUlGMEVLVGY5a1dER0oyOGV3WElId1NDTWtWSVZFTkpnYXhDVW9oUFBfZ0l2NE9RemxSaG93TnU3bHpJQ3RvT2UtQUFHbHBjcUZKTk1VZEkwSXY0YnFlcDUxS1NoZjFnRmp6ZGxMdGRfQTFDMTY5Y1pHTnYtUS5Ld3VSeXJFWElWWUZpNDV2MmEyX0pBLnVvZ0pRWktBX2FRdWVyY2xNOTlQbTZjVjJuTDFMcnMwbkFaa3UxR29UMjU1Y3MtcVJBUmRGYzk4bTRrdGVDd3ZOWUE1MlA4UXJMMXpXZWNEbWw0VEZZN3dvYXFXS1I3aFlJMTBQOVFWdzJMc25IZEFZYUxnb3dYdTFwaVZQSUYtaUZEb3JmY0ZVeUJQVGhCZDBhZVhJVllpd2c0SXBkNTdodHczNlE2Y0VHSVBndld4eUxkWDl4TEtBQlpuWjFTN2xZQ0VLVnJvZUl5MUVFRFRxdWRSOGZ6cXc1WE1jYXY5NXdJbmtEOVg4SGdLYWNEaE5ZSU4ycFBPc0ppWTR6Q1ppRWRCUWxzMWlYSjZNd25aem8xRE1yQ0tVWk5FNEp5aFZ1djNid01UTVlxMmN6cWJZSXpyWWNjNHpnRVI5YV9wS2x0WEpyb08zLTQxNC1jUzUyNlc1UDAxM2FjSEw2d0toOXhObi1wNXFvcEs4ajVfTVZJZ0lRSlA5V0ZuMU1VdklrZUtSNnNDOXlsa3RMVFB2M0JYb3ZRcXg2NVpRSXVPSXR1MnVIamgyUm9sS3h1NzVfMVpJd0NVSlFnNng2MFRxZ21QSElKcmtlUm1EZTI4VHVCNFh1VkxVbTcxd2JlMkVvZzk0Q3R2bUlpejBxYU9aQWJDZTVXSHlOYl9XQkE5LUNFbG5BTHZhZEx2ZTk0QkJnaU9FNnpzNTVUY3ZoQUFVM2dMSW16ZDlwaFJWMFdyMW9ycDF0dkl2dHk1SjhSaW1EOXZac2EyQ0VDYVdVWTNQLUJtb1R6cWNldlBoVWxndU5lU3gxRXVDb1NucV9wbkNTV1RFMzZIVkJLaU01LXFyYUxGR2dod29hQVhlb2xGd2lzUjI0al9RcDdJNEFiaVNLd2UwdUc5ODFCTVE1MlhXOGpDTlM3YzdZUENvTldNUV91eDlwWHU2cE1SSEVEV3RZc2ZieFY5UWVzNUp3V1FoeDl1VWU2ZU5JVS1tTlYzQ3BzOUZwQU44WXU2Z1V4d3Rxd042SEpvQ0prUHBkOF9CS1dHcmxhUzRNdUpweU1sUHBnOFpWTy01UTUwRTRTcWVOSHZlTlhhdzFxYzZlMFhzdWhEaVpfVm9LOTZ0eHFMRklzZHdxLXVIWGlSMEF6S1VLTzdxSjlhT3R1MHBCUE52U2tRWVA2UXZYRktsSkIxaDM2SUVXRFZfaElHdmVmSks2WDUxdHptbmZZZGVuamZybG5taUFQQXFDTTVUNmhWTS1jbEhicHc4Ym1adE03QmVpQURCam1rQzNab2lxOVpyNWhfaWhvUVFGLWJYenFQUHJqeUxwM19sZ2s0b191dnY1YlJlV2FzZGZFSHJiVU91MUVnUDNNNWpveTBLazllOFNQZk50bFNDNDlxdWpmVW5DTHhVWVd2VV9JWDhhdXE3S09UM3ZVRHlJemZ6ODNvaUlGaXJvbUhwbXBEUXlkWjVpR3E0NE1tLTNhZlF6ZkRxTTFrSGN1MjVWc1pxNkxMSlNKSlVDT09lMnd4Y3RMUk1NMWJrUDBET1pNcjZFMkhUNlo0QTVUcEw0Q3haT2dxVVk5N1Y2dkZRWnE3ZWo5OGpiS1ZZV0p3OU50eTVoOTRSQmZiMkFZRk52UEdXTGVmYWQzaVZncURaaE5MeDkzWFoxNUJ1anFfMlJyczZjdk9vNmkyRTZOVngzUkQzdVpYdWZ3S1JVU3BsazJCQ3FnbWlzT1FHQ1BzcHNjcVlJT1Q1UDhjeWtmUV96NXdDbGlQb21oV0lubmpHOW9CUE4tZWcyMU1KbUNmeWo2T0xvbzVtdUN5TDNnTkRJa1RpOXBKRG8xN1pJc1lBV2ZtZnNxWENuSElzMDdHRlBIdHlNZ01rQUNocGxZdmZETTdLbW9SVEdJTmRhenloSURxQXlaQW9QSENvanhxb0F5Y0NMRGM5cjJaUkpKc09INmNMVzBtQWM0eTYwdXlDSGZrak1XZ1ZoTUFENjYxLWtka0N0UEI5MHcyVmhsSWgtSzVmT1oxTWVRX1RMSUpyZGFPeFBjbkNHbnhsNnVJR0pzX1ZhTXRHUjZKT2VzUXQ3MmZYWjQtWjFKQmVEZ0U3S240RFpKWVRrQTY0Z0ZJV09vZFNCaVd6bnAwNldPejBMZU1Db0Y3cDhFZFlTQi1VZVVHMEw5a1lnWXBQLWlYRFpHTk1YY282VnFUQjR0dEo1ZjhiUzhrVEVkQkMtTGZyOW9tUXRPSDdjcWhRZ2l4ZkRScmYzeW1uQ1ZUNW94SzZvcjU2a2VjRU81ejVFQWt3aDRNQjY4MElMME1VMzNZQVV0NVZuekh2M2hncExQdDYwS1B2VVJPZlJoQmpGNmdzUTZrTzBicDlmOFJjU0RBdWdCbkl2RnZTNkNXTDN4MXRnWklZbVFVSHdJOG9ZX1k5UVg3ZU5udW9LMVBWdWtjVHBBcWZTUGtuV3NHNVJiZkNPQS1DQXJmZDRsNWQxZ0RrZzdPQzVrN055NzdoWHFhMlF2anV2MHdJRmFKRW5rRFdVUFpJU3VpbDM4dTl2ZkVVT2dxTlhmWTd3ejdlOTZ3bXQ4RDJ5SG1TZG16VE9wZEs1VjBQU0dHUXFnTkp5WEREMjIyQkg1cUVNLU5JbHhQcUY2Y0lhOWd2dzZVRVVJVDJlYXVVNkJxN2Rud3h3Y3lHNGxRTTFlYTZ4T19USkt6Y1lySzdvREtBamQzT3RiOTNPUGR5bHc1TV93NzNfSzVlSnRXUEhXeUhfbXV6aVAtdldCd1RLdWtnaFI1c005aHJIT1lOOFJFQmVtTTFWSmZidkgzT1Q4ZmFEZDgydC1RY2ZFLUtLNUxZdURCaGJ0UlItNlBxVDZYQkpTM3h1bHZ3RHBRUVU0SGc3U0xPTTd1MlQ5UGthX2ZURnpZSjlsZ1MtOFhidkR1VS0xMnM3bUVJOTR5dVZoaG1jaUpFcUJOV1RmQlRpUWlTbVNmVDlzeTJQRGk3SzRPMW5hNmRidjl5V2duTHFNWG0zWHFoaVVYTzFfRWUzdF85eTlSX0lSV2huV1RqNlRyMkp4a0FhMjVzVDk2MUU4cGMzcWVwM3dhM1V6SnYxN2FsTkl0UEg3bWJidlZUX0F4UUdLcW1JNjZ0MVk0RG0wZzkwRFI5SlN0aHFUT2V1VWpBQzR6V2xpVjM4dmxYN01BUHlKT09tNDdQaGlzQXljcHBEd0IzR21NRWxwV3IzWC1zWndPN2JPdnRkRW96bjZSTFZKZDBhWjh5X1JMeFdWTjY4bzdCaDhZdlN2bWszV3FQcHFOc0JYYnlZeHVCTzhkX3o0bnQzSk1iRDdNUXp4Sk5JZ1otMXlkZVBSalBpSFVGdnhmTWpvWlVyMnh6X3NvczZzZXNDeU5WeW8zYk5EalBsRFpYcHAtQmxmYkJTV3JJS3diUDBSODBFbC0xYWdOdDFHWEZLRHM3LVZadnNKYVM3Q3h6Q2FUU0pRbUY3TXVfYUY2UE1xVHlJWnRyUldWRHltX1pxWWpQZEcxZEZTVkEwbDRsS0JCNEEzS3FwUHM1WGFyYTFYUk5NVnFVY2ljWGUxbXB1SEtJbXRMbENYYy1nQUxVODVES1dEaEFxc1pUWk9uYkNxU1M1eEk0SmFiSzZfYlpfN200VVNVUUtwUmd6RUJDV0ctRGR3MlY0NXNQVW9Jd2UwQzA4RFBNWVE0MnRqMFg3a3VmUF9LckxwaW5WbVU2VDRxbW91MkhPLUJpMGhjdmJiMjNmaVBfYXVfQzdQM2FSdFQ0ZXZJVU52eFJyWUJZaTE1MGtka2d0d1dPdnhiUGlXcjZaZ3JoRE05ekdoVWZiR1h0c3pxdEFqcEw0LUVRTWhPMG5pVkJia3dLSjBNbW9Xc3BnSlpoN2YyUGRCYTREejJDS2paSFJqZ0FTMXJpcVIzMURqLXdTaGhnUVNQeDRDX3hZakNsMXRoM01qLWd3ck9ZUDJaT0ZVSnBHbWxWN1VvbnRDN1huc2FlVVg5SnpRdGhoVVBvWUxxZHM4WE92SFN6LWVKcnRpY0Exb2JQSDBvak5MaFNiSVo4cWNydWpWOUF2eU51WjVCZkEzZjdDWHhHY1ZHLTM4Ulc4eEFGdlFiSHBBZGlDNWtwa0lqbE5WQVNWWmplMzZwdmdwTVozLThuOHBKQUNST1l5YVdmUFJZT1RScEFCUk5kdTFpV0ljVUx4UkZrblVuY0YtcnBGNDdwMmt4U0tvVW1hN2VvOWJZdUhKRWd1M0dOaFFvVU4yWWh4Zmo0VGpENGxXTmVjbDVPNDdFT0tDZTIyb1AwSmdLYm54Qk5sTWx3cGp3aDk5Rm1SNFVocDJCME50NElfcjhRRFBiOE9NZTE5VVRGWWdxSEZJUV9OaWNOcGJLbFBqZU9FMVlvY3lCZ3VmTERwWGx0V0h1WU5ZOXl2S200TE84a013U2NXenBlck5lWE9jX3FNaDh6el9reGpvUFAtNkZpekVIM05jUVdrR0xOekZSWlRaeWptdDktNWZjSUp6ZEtaMnF3TlhIQ2VUdllpUkZTanMwSnQ3a0FfRmRZZU1oM2M1NlVJSTlwUGtMc0F0R1g2ZjBKZ1RkYkplOEJSX1ZTQnRCNHpTcXJWaFhJR0pkYmhsVzg0ckxXMUhVTy13SF96V0t4Mlg1ZVpyakY4Qnh5eFA0TmY3eW92S0JUalVKeW5yUTFTSEdEb1llRE1ldXJ1bmQyVzhRdThoQ1owemU4TUpHaHQxbVJSSE1qcy1rMWd5NzlWVVEycGVUMXV4ejlEREtSU0F3eG5sU1JMOHdmdk9sbUlMOXZjVXNWS2FXMEJaWHJGVy1ONHpvNUw2TjI2SkNEYUNJTk5xUDFNUWREQ1VGdUtwNGF2MmVQWncyQ1Nnc2o4MURVSjlfVHljWDl4YjA4dU5hRExNd01DMHozM3ljNlF2QnFCWWxjWHhiMU5oeDlyRjdzMUtUUTNaZGhtcDN6bURKVkIyMVc3M1BiUVJUaldqSUxISVprVEhGQXg0amNjeUdmbmNFZTBuTlBYby1WOGU4bFBVV1BhZzA4c0VlOWRaTms1UnB2Z0phdTNYZnpuZXJVa3dKeUwtS3poMk9fSEVIMHo4a0I4Q1NtY09BVDh2UmpicUpnOXd0cnJ5TE94Z2hnRU1DMmtXNXBsTktnN0Q0QkxZQk5JTm1GREF6U3ZiOUxBemh2X0lpQXM3Ui1Ca1hhWXdnX19wMHJiUFB6WFVxTjVLbWZPSHlCTTQxRVB3WWtEMzJZeERGWEx3bEdkOGdrbXU0OGZsSHhqOUJ3ZmluUTZxZlNGbksxMXlxMVRTQzE4d00ta1FjNFplQVRPdEtnRlY0SGJjdzVrS28takc3R3llY0JEejZSbURpWUtCRDBMSHN2Z2Q1VkV0b0k3anRvZENDM3AxUm1CbDlOanRzVkt5LXVsUU9yb1RKNm1BUTlmZjRsNTdEZFBiQVJ5cGZ2X0stSFVSaG1KcUE2cW0tNGNlLU84MDVGQWNieFNGdzM3MnRjWDZ3bVpmTWN2RVExSDB1V3BsdHFrLUJaa0Q0aFJ5THVwNDdWYUotYXJ5czg0dXlQMmNfdjRZMTNBOWpGWHVpeEJCREFFSk84QzlTS1pYZjdMV1lOT2MwQURZc0tqbkZTMmZNNXZCTmlsa3hab3VHMUFSQjhUdFRiaXR6MUdObHhVR004NzZwQ0pQTUhpYXI5YjQ1aVk3XzAzYmFHSTNwOTVnMU5GY3gtdThiSHk2cHU4NjB6bTVmOVBqTmJzQjBXaGVsbFh5TlZjbGdWTU1nVTVMZGxIRWVQd2FvTVJPY1YtbktNWXVCWVVvNHBzYzhrdHVsRXltT3JjSDg1NkFZcFFuRWZ1SFZ3Rzc1Q1U0OG9Fam1JNTNPemJGcUJrY0pyT0FqZ0ZZT3ViYXg2Mjh6TzRYeE1JeVdWdnY0TmJqbXZ6ak1NWkhhUUhoOWdDNXh3ZGJoUDUwQklsbGNGaHZzZUFidU9tNmQ2ZzVvamRvTnNtTU1EMTFpSXpaZU80d2VubHk0XzdCODBfdE94cFFXYUVUYTJOUU43b0RlTVczRlJSdTFvYi1JVWFCNi1zMjF3Qy03X3g4SFEzZlhGbWpuNlRKSVVoODhSaW91OFhRaGxPd3pUNzR3dXhMdmNzU3RMdzRyZTJaUGJGNG5nSUZIN1F3aUFqUWwtaUF5cTI2T1RWeURrSElNdzcxaHBwdnU4bGxqTXVzMnd2VDRfUUI1Tm1yUWF3R05DaFc4Y2JWQjBKbmF3bndBZk1EVWRCQnR3YTJVZTBCQm4wRkdKUWhDT25tQnJNZlg4bVpZdWROTExFaVlOTDB6ekdkNUNjTGdZV2RmM1drYkFmLXJhYy1Va09mNVJWdVM4dVRaeEtOLVVsSFhsTjdfaksyRTM1a0pHSWFjeVBQM2k3cTYwdE5uQmgwTUpyM05McDVoMnVydlVxWEJOZzVPaXhYNWU1SzVxV1FzTHdTbklOUDVZTFVfTk50bzRlU1ZhcVJtM1M5dEY2V1ZROHVDeUs2enM0U0ptRUY3RmM4RVJ4MmhFVFB6SDNyMmFKTW9aTmUteXhLMFVxM2RlS1NZUnJ3cG5MS2NYak1wTnlWOUVCeEJPLU55eGpDOHkweU45MFRBdlRzUEQxQjBfVkhibkFVWEFXeERwNFVLZFpaNmxLbFlwaE1nelk2NjN5MDgzRVM3UTQ2bEFEN0tHQ0NRclNrLUNHb0tRZ3hYQklfX3hia0ozeFVmemFsRHlLQzZzNmlDVThEOGtBbUxIcHB1bjk5R1lHZTk1UWtUX3dMQlhDVU9yM0dxVzRnaGRVQjNJNnp5b2FJVlBxWC1xbjY1WlhtLWhGODJKYUpYaHA2Q203ZnFGUVVmcl9XQzc2a3RrcDZ2X1ZITFRfMGNERnRXcUhFZnNmSU1yNk9rYTJNUVRNM0RGTjV4dDRNMjBlVnNQd3pBcVduRnAxSVItWm5TRGVYWDh6dkdJckpqRVUyeVpsNzJFRWhQWVNSZDhtaVVPLWt5c3JFcVlJN3ZlbVJCa1ZoNWZsM042S0RiTFJtOFVjZWhHU0tWZmp1Q0phc2dJYWZjd0hCNkd2S0FwWGhLN1hqVExad290SXR1QjlwVGFvNHVtcUdiUlRqNzR2R1c2eU44aDRlZHlweHV2WEh4ckpVZGdFWmU4TGdTZ1BfbmRKcEIzVHhiV19pNk9FYkhWWVUzdW12SHhuWkE0YncxWUVUYmRiMnlKQkdlUzZvcUhWT09MMnhpa1MzVmZWcVQ3ZkdoTWNFcWF1bTJJaWNZd0VyTUstTXZya19rYU5kUklkVndhTFo1N0NCWnI5RTY3TnFTQVMxUnB6NnFseVdhaWhVWmpBQjRmWjdlQ0dmUGRhTVZJTlVTNVRnYlptb2JPOFJNSE95c0FsaWsyLU1oc1RVd3VPV0RwYTVRZjdiclhrTTFnc2hpMV9WRG5fM1JKMllWTi1yZTA5ZFlwSGREUHJlS1VnQzdUUlIyUmVwREp4UV9DdzdPNEctenZ5LVRTbU5tenE0cHNkamNJaFBWaDZhN3l2eDc1MnV6MlJIcjhJR1FRRlVWVTVIaVpuaVNHdlZNZVpxWUpjQWRNczRLX3hJTlZxTWlCc0o1cjNPbUh2YXNnMHA1d28yRnFBOEtVajRRalVKd0RacFk2ZTREZVV3LWNkTFBpMHo1OGhnS0hFVlBDQXRfRjNtQ2MzZnVMdFl3dHA5b2I4QW9WQ3lHQkwxWlFsMlpLVDF1VVhqSDQ4TXRvOXFqVXM4YWhidEhyRm9RemNaVm1icUtCZDJGTHRJSjAwbnN2eDdjMXVZU0drVV9NdzZwNXhaUklnTnFNNHRMT3F1OHRjbkFzbnVDd1pfSkxLZVFFQmtGdnNjOUFWVEt1bnpKOFFQc1E2SEtHcDZwSEJYeE9OUlhyQlVLaFI2THZhaV9jQlhzd1M5U0s3VlZQQ1JsNEFGclF1WExxSzE4Vk1aVG8xQWlrYU1ESVUwR3JhdTN1ZW1ONklRQy1LQ3pFWTBOMktuZXYwbU5yRHdXYjhnU3RNU1J0azF4bXM2emdzRVBTWVVDd0ZRc3dxMHlEcC13R2Z2UVJDcUNERjhMZTVEd21BUlNqeXpqbW1ZWEE2YXRMeXhNR3JOSDYtVGVJLS1YTTlUQkdHOWdteUtFRmxLZWQydDhqUVRuZ0VPM2xnWVB2ZVp4OHRjeDhLWmNvUFdwVjJxOEFxMXZXUkhpcHBYN29BekJaOExsbnVfUWIxVjdUR0Z0ZTE2aHFNajVTenNPeVJMOHBIRXlkYXdnMWJ4MmNCU2Y0c01tSkJCd1cwTHZ5OU01TVE5SW9qbWEzRG8xSzJsT2lEdUJYN3QwNXlXVmRFbTU1Yk1JeWdTYzdhMF9uT0hyWnl2OXFEYUFxbGRaQ0p0LURtNUR4QzFWS2V2STI3N3NjZnhwREFMZEJYWmV2VDN2N1V0ZkM1bG9hdVo5T1JsZjZ1SjNOOElnVC16X1FRNkw2Zkk3dFNwUlB6LWFpY0FwYl9MSVV6Y0tZYUhLZURRRXMxWUkzc1p4aFJOZngyeGwzSXZ5cVI5dTdoSUt1ODVRMlk4a0JuVHVzMmlmZXdudlFTUHY2ZkxON3djZFpWOEUwREZrdW9icmMyT0MwcWFTNkxseWwzcXZoRnBrVlRUd1RiemJiN3pjTVpwZkUtaHV2NDVZN2hHMG96aEpUVW90R01PY0ZaZHphem5hUFJIZEJock5DbnlnSUNNNE5xZnVKdmQ0QzdoaUNpQTlRVThSMGpmNFBpTWxEVnFsa1BOc2JXZW1JdEFvTU9Mem9kRjhjcWF4SWUtLXpOTDhFTDJhQnFrUlVBNFdvaDhoMXFWalBTQjFSRGV6eENhZ0RmX3dGR3lxWlpHRlZ0VWZ1X2tGSWRUelFnYzY4NWw0T3lLajJFZkY2M2YyN0ZGOXZvSnE0Y3poLThuWE5VRGlDS3dfVEliSktjQ0NnbG5CRWRYSG5IcmY2ODMxZjRUTXZNUFJnM2FScXRJbjVJZGpTdWIwTzJRemVwRzUwNnJhM003T1h2NExKVTNXYTVCVkZublJWVkttdW1rb1NtanltYUU4ek9aZ0ZFX21yNEdfbjhvNHBrY0lVbXRQdmMxaFloOGFTRnNjWlA1Zm9DUkFEYndqblQ0TzU4SUlrUXU0bHlyZFVmMlFONkllUjlYWHVmVXloZkZIVkkzbUZ1SVdORGI5THdMWDBqempYRHZtVWdXcHA5LXlmNEVYbW9KcnJqdUh2X1ZtZTJtX1ZGdXdZalZUOEhoN1pPcjFrN3pwaUpvYUtHVHRHY1J2ekFaOTFGVE15VVRseTFnM25YeXZQamlOTjV0SDVrQ3Z3OUNfTnprcTZBYTRwM1AwY20zdC1KYzZ4eXkyY2pFeGFLaTVENzlnbEdVcmxFVG5Eb1FzMEVCTFRwUDFqMVBsM2VMSHZLT0dGVXdiNnNjZGUxQUE0elluVTRKSjgyTUxXamhSYi1wWmhwT1A4OFNCRWpBbUs2NERLM0JoWU5UQUdrb3M0c3pLb1NMd3RwSWlHRU9maVExcUlnWllDN2dZR2o1WWpoaFlHTHUwUGVKNGJjS2NKcjdqamxTZlZfb3lRRG8yWTFPbHBsWDRrNnJuc2Q5VmRXOUNVZGF3clVDaVo1THFvTnczeTd3T01nVDBEX00tQU4xT2Z5VlNyZXdPdTMwZ2ozWEk0Y0hWZUJKdlZzUnZuRVZ3Y0FrZW1KNVBHNmt2WHctN3diZEZBb3BKOGc1NkhkTGpRQW9MUHFZeG9zMDFHUzIzLWxGeHNTbnFqdkNLTVFnTzRDc2pNN2VCRWtZZ0xuaWRuY29sOUFxZGc3aHZ1UkI2aXRzLkZVQzZzRHhHRU5TWFBnRVNhWlNYTWc"}' + body: '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmFvY0lxQ1Z1WFpwOTZLSTZQdmVlbENFZlMzeVl4ZC1yRm5rZkNTcmdXUVlwU2tzUGVpTmJENFZuVEdEbGR6dE1GRlZ5SGdMRHVwb2VJWDlIU3ExTEQ3MVFSNWpYQUE2aUZpendrLTYxbkJ5ZFY1SEVLcHFpdjJwckp0T0FRTlQwZmpsWlJadzBiSG9zSldIenF6SDZXZktQT25pOS1GTm1TV0xvZFF1cE84WGU2bC1PN1RoWVlhc3hoLXJNN3M4QUJrekZEUkVPUm5rTnczMVJ6SDAyQjJhOGk2R3VfcUVtbTdvUDdOTnJRaTJfRGJ6YnZBOFROdHBwU1gwbnc1c2ZnaWVFWmUxTmZwVXNSZDcxMmd4YWxWNUpuWklJaDAxcnF4ZUVZaVRRNlhxdXhPbkZJd1RaTTV6WTZSSVJ4QW5rTk8zb0pMUjlkSTU1cG02ZVlxMnFoZy5DUnVlaDRPYncwWGVkMXY3TU9GSVNnLlZRVXdIMno3UWV3SXJZN0kwSXhEU2h6Y0FLQmlKdThBVFVTQWZvUTBfd3J4WEZBNkxyOGlhM0k3clFMYm15X1hLbjNwa3FFR3VnVHZlTm9BenVmaDJNR1pHV1ZtOUJibGhCV0Z1OTZUS0s4cVRqOHJHUHpaZHB0MHpsSlloOTc1S3hZSnljNkdpNExzMVVqVDAzaExEcXVpYm9SMkZpNk9oZFRQQ2p5MXd3OXlNLUhKREswUHdzSUw1VW41YXFGaTMxUW52VlVja20tZUVNRFlJRWwyeTNtang0SmtVS2Rtc3FudzYtckRRcmd1OHFxV09NNXpDaFVKRW82dkQ1bnVCLWNLa2hJS0JPOEItM3RVbnQ0bjBQWldGSzhDaW0yb0dyV1JNZWtjSGxYdFp2Z2M0OHhVRVdQdjBhQ1l5QlhzOTlac29DN2ZRd3JIUllYdzZXNTFycUJqcTA4ckt2YWhTaVBxb2dkMmNfLTB0VVlOQ1BUWnBrcjVzanA2TTVvZUZlTG1KOURlUnVES3I3bWg0eTc5UndWam9xZ1JGdlptbmVBcmY3Wi1USmZydmlLZ3lLUjlUdjN5ZFVjNzI3aGJMTGQ5NC1LNFduMU41NE9paUtRNmJEbjRoWTd6a283aTdHV2s3NmdXWlRrRGFlVTFtaDgwQUE0UGEySXNsNXMtaXNuZkYwVTFXNXh6WUZaWWVYUnlNSDZkcUpHYm81RDBnNzZhdHdvTjg4eEZlNXNDVEpYQjBnQTBRUUNRNWRlX0E4WS1rcGFWakpKNDJPQ1NEX3RkWWR4a3JDOVZwZ1FlM0Q3ZzA3QmdqVlU4aHdFZTVkTzR2QmlRekdxcGtxX1lqWkRzSTcwWkFWYlk2UTE4QjRoWnJFd3VUZEs0QVdfS3Q4YmZiYVVuQlE3d2Z4cnJ0SldZeko4eUJaaFNOV201MThfekRoMmdYLTBuNjViMm5rUlVFckszWXBEVEpMZ0RZMnk5dGtETkdHX1dJUURIWkNiV3dDRDBTbDBheHdybWxHYmYxWS1heEhudHQtd214ODROZ09DcllEZU1SUnVkbjhScWlKSXF6d3I5N1JEU1pQMGx6dVZ5Y2xMX2RRSmdTYmtrRjVTdWdZbHotOFkyN2wybWV6VldrTDgxZ2w5enVMb0RiNmFMalA5WFBqdjV0WjZsOXF1azZCcjF0X3pBTVpadzNjUGZXOUVXa2hOdGNNbEZITXBYMlFkUTd5enVQSzJEb3V3bzVWV3gzQUFTelJCb3FJQmhIZEZLUWgzNUVFQlhHcEprcG9USzktTWhINkw4Tmd1dl9MVHJQNnBlOXBvS2otTjVha1VtUWtDY3ViTGRSUjQxcnFUeXNoYUl5WHBEYnVQZ19pbUF1eWpTLWR4VlZwb0Z1bXIyMk1aMFgyRTN5R0piZGg4cjVBTk1mM2lwZUNDdThOeF9VWUxubGFqczlMR3M2bVBneEktUFY4ZHBoUUxwa2dfM1FYR2xLOHcwcTVJMU1uMFNlc1R5NGJiMG12N0ZDdUYySzFKYXR4SGJqY0FFcmlpbm9QTk1yYUt4X29Kb1k0cVc0R09rYUFkQjA3MTBnZUJuYnBXcENuOGJNWkJ4RUZHNE1XU1lfbmV0cHFDVUhUZlQ2S2QwRzU4LUZQUHpxY05naXNGMUVuY1VhU0IxTVJ0YXVPMV9rSHgzT0RPNEhfSm5nYnd2bFRENWdXcjRpU1VYYldSdWQ0ak9YNXhJYkY0NzVyeGUxa2RNc1pORWhkZTVHbU1aQm9lSUd5VEM5bDJpTXNMQzFmdXp3YjU2SVczVlRmTzlTazVqOUN1RUdsMlVGcGd3N011U0laQ1VhSWZId3V2bEpXUXp4QlkwaG9xdEtSSjRQT1dWU2FhRWJ5OUdMa1VUSmFEV0p0YWFfc2x2UkVnb1E1WFFWeC1oUHZaWFczWDJlc1JsanJtQXZnVGJFWDQ5NUpJbUNsZTByaU1adjlETE9OS0k0a2NzUU12cGNsaUpQWHhsUE11X1dfZ2RBejExdHM2RUNGY2lVdHJOU1RMU19Qb0dqSlRSa2lEcERYSFBFSWpoaVp2X2xES0NUMjF4b0VsUE9kekk3OTYtNGpaYUUtbFBxOFpmTjdzQ19iRUxtdW92TjZIOS1yQ2oyc3BuczhLSDZzQXliZDBFNmxjTU42UHlCRWJWazU1R3V3UWNrLUNjaDA0M0l2OTJVVmZsNmJROUJsc1ZjenFoMlVGV3Y4SC1qaHVFeF9XNHZidFhHRVdsSDVSRDdEWlUwRmNCbkprQW5senF0d0tnMy00UGg1bHhTSWppWVJpdTRKZzg5clRuLXZIZUZnc0hBWF91TDFjM2xSZkd6MmVtQ202Z0RrNVVKdHZSWGtmUHREUGVHckp0SVlTRExqWjduaTlpRXl5X1JFVDR0T3NEX0R2N3hieXZ5SHFHUzZOa0JLRUkwY0g4OWpTVllVaGRxZk9hMUhqUU42OW8wRmt3OHE1TG51c2NXSW40UFVyajBYOGd5c3RIMWVha1ViSU9mRm05RWtzdzh0a1Z3S1g2LVlMbHktUS1mTlpwVXF1Mm9HbkxOcmV1dmhoWkJHdnJzQ0pzUVF0NHRjTWNwakc1dmVRd2RQT2l2R0tRUmVCOE9MazJCR2RlZGZXeVlyRGk2d2ZUclJ4VjZyb2tWUlpvVk85dTd6ZjkxWl9fMHZaRlFSN2ZubE5DVHpEM1dpOWdUUEZJaThFekdfS3BiRHNmRkRNN1lCeGx6SWNkeDdLZW9aZXhHRXpUdDR6T2JfTUllWTFGWkpFbDdJRUxxUWJydXhFMjBURVYyYi1VaWJvWWpvU3hRVUNZLTg4TmJ2czZCWWF4NGs2eU9yU3ByVVg1OXNNcVFPd25uMWRZUlFaeHZ3WGh0U3pWS2d2OGtzYkNvd0RHRW5pb0cxaUJKa2NDR2tlbUxPRzlMb2xpOWxRVDJVRlpBR0VzZXp1S0h3endPOGprRkEzODk5V1JXWHBqVFRuaEN6Wkdqd2JRN3BESUVBWFhyakIxZmJ1LUZhU2VYUGVhS2VkZDdMUy12LThJaTJFaXVLcUdIaWttV3MzaVJQNTNTN0VkNlhtMHJydHpSVE10RlJ5LUdQOXlWWlNiR3JyN2lFZ2YtMFhLT3ZPZ1FrQlo5enFua1FIM09rZVlfRHlNQVJqSEdkbTRIYnQ3d0VrZjM3MHJ5M2JVNTlud1ljWEI1b1l6YUQ5STVxM0Zmc09yTWhlN3hIVDZxZWRqbXlYRHNqdi1DQmJVYUVpc1lPTmFUUW5TbF8tRjhBZU4yajJrYmtCSl9tYzBSa1BncHA3cjZJQVRCRHg5dkd0ZjZkcTRpcndybWY4MV9SM1dfQ2ZhMmZlZWtzUDBFVjB0THVUVGloM0lEUlR0eU80MjVEQ0pCSm5BVGNkc0tPOVhuTDl2NGlMbG1Bd1E2S2U2Y1N0cTNTelNNUXhLUDI4TnlXS2lKV3NLemlMZ3ZGV21zQWQ3TG5Gd1dzWUFRa1ZidjR1WlZVYXhIaWJ4OUEzZGhsZFV5cHBEaUxlcWswck12U0JnTHNZVWk5SXJHbkdfSDdISzY5bU5CS2lNel9abDdBMjBLbFlJdlRVaUFBbnFLQi1kdnlCdmNVN3l6bzkxY2l4dUVidnNtYkNFUy16WVo0MUNLWGoyZ3BTZEdlUDRBVHhOYnV1eDdZWkFPUnlFamdHYmlJS3dUN1ViU0ROcEZEeml1eHBVby1jZmxabjlZZTNNcHFlLUI2NXF1RElxcDNvelJvdGt2SzVCcUpXMkRnTEVvUjBKZTNxb0gxVVJTT3Q5MlY3bmttSWRrUWloMURuUkttSEczQUUxS2YxUS1fRFJGSmJxTkozRklFYl9ONVlMT2hGaldmN2p5ZG92WEVqUnhvYWl3ODJOR0tHZTBOZDRhNW5JanhHbWhuRkpVVkxEajhnZHNyNG9ueTR3RWtsVnVqdG1DU1prb2VSRm9tQmtfNjlEY0pFbVl3U2hlY1JJdktxeWJ5MWx2VlNWTTZuSlpPam9FSEE1clNBVGxHQ2VxbEdWaVB1YVZGdXhmOGs3S0FwcGVFMzI1aVJXVzRhU1FHeE5NVjNWWHpIa2dZRjg3UldnLWNWNDI5M0ptSC1IOTlmT09Ic3JiVW9oaFRIS0E0MFMzV3kyTWc2anllQ1Jldko0SXhYVWVZclJVcVhBWnVOalgyM2pZcExKYTk2bkoyZEwzcHRiMW1WNF9vWnM1eG5pRjhmdEFEcTZfTDZFV3dRdHdfS0pDVkE3a3dGZHBOaFM4YU56V1g3d0ltNndRQ3lsTC1jTHQzSm9EX0ZtWmhfeEhmOFludFRUUFpwaVZyc0dpRlFKRE1YSk9OQ0FEekFlbk52cFVUMTIwaEtiX3BEUjdDa2RLbThQb0t6ZWU1UzhaN0h0NTZjYWltOF9ZSXZJLURZdWFuOTJ0ZXgySk9Ca0paV2p1Mk1LZHVOUDd3UzQ1dWZhTHZyT3dKSlRRQTk3eHNnVERXejN2WGJmSTVCX2VHMzVJVG9RX3EydXJOMlBfb2NlN2FRVU1mTkpzcHNQeTBkckVfekFoUW9QclVPNk1uU0dlS3NrRVAybHJzU2dhNUpsTUZyclVNNVdtMUREcW9oWnFqTDZPeXlPVFVvUXRrQ0dQUlVTM1lmbVFJcVpQV01NSG1wbXlwZUdiX0xSVDJXQm1hSFFOUVhfdVNWVG1LUWIxbDBJa1R0eEFNQmQ2NzNTQUJNVVllQ01zUWlXRWdxT1dBbVk3MjZWeWhneFg1bjMycEV5d2x0cXFGLUd2djlUMm82blFQRnVrWmNSc3c3TGROREx4V2hkMHVyTHFneDVyQkh4QXV4NlllaE9QYV9sYVR6WUVwUFhGSm5yR1c3LXpsYkU1UmtUUUFRNm8zbnh3TWkyLWFPREthSmxWS2EzNW5ZTHZPWUZtSUg1b3gwcHVBOFZGUXNmVGdGSjZQa2U5cWxic0JwcW82S3F3di16czIyWkxhWVkyNzlhRkUySXZ2RXhHakk2cW1HV2Y4RTVPbjFwOGZWSndqQXlqc0gzLXlwelFJOWFMYzF5WHVWcXFvNzVSbGlHZGE2Z21Ec2ZSRW5wVVNrdUgtYzVXTFBsX25jdFlmY1pKUU1IbWlLNUpPT3pnTk1IOC12ek1FY214NXJQeHpuTHI2cV9tWjhKb3MzSkFiMXliYm1PNmFoR0IwQ0hqZ0ppY0FOdXRQME1SMXd2eVd5Q3ZVUzFpclVnQ1RyLWNYOGFGblNKY2REdUxhMWY5TVV3ZmJzZTV6YkpTcFRpNUplckc5Sl8wLUwwajYzc0Q4ZkxyU2l2MGRiVnlhSl9SMTg3UWRWQzRMRERWMzhhRFZNM3FOdmNwYm5MdzBtYlZzZ2NFX1czTmtlWTRsbDBLa0I5T3BmRmdNX294d1p6T0R1S0lsdHZ2YVhkbGZha2E4a1BDdXJxS1BJZGxydC12bHNLdkdpQnBjNXVTUG12aGcyUEV4YThCQ1BBZHdocXo4aDl4Y2YwVko1MWtPRG44dWFWOVlzUlhtY0pOdzhZTXZXQXJnOXU2RnJzaWtsRXQ2NUdZbnZETTN5V3V3WlV1UUw5LUVvUUpCdTM2bUY3amwtQUFrYTRhUEZONWtlOVdlMEtObEtRZDFOVG9KR2w1dmZBUV9UQ0FXQUtQZjlGSjRyOEpzVFpWWW9lZE53OUJzQ2RLX3gyQ3pTdkZubzgxbE1JT2pyYWZLT0NpZ012QTBoTkM3NE00MXo5alZ5S1d0T0JXdVRWNEFFbGtDS1FDUXVBTnVmRFIxWUNoX0lsRzhVcklFR0VIdTdGMzhHUWVvWmtCd1NNMHoxZlEtZVVPUi1GX205ZHN4bHYzNGh6TFhUckx2ZGJ6YzJuYkV5R1pnSGhBWGVIZkx3eHFNWURwY0RmSW1XcEwwekJlT3JWWkNjZjdwcmNON2FRUGxSbTVYUUNnLWlVOUY1VXdkMUVQd1pFUGlNTGxGX1k5cGg2N0lzemNReG5VTmZZYzg1bnhIWVRBUzQxakFicmdJSjdfSy1KMDR5MVBib1lDb2dRcHN3cGlyNGZNdjR2c2RuMUtVbERxdkh4UjdZTkNXU1l4dlFmek4tNzUzazQwRERYd0xlXy1yS0tzSUo1LTY1RTJXYzZkNXZYLXpzdmJTbmhDRnlidE42X2FIZzZzeHlPc3lqTnh3N1pQVER4QXRXeHlZOWpZMzNnamI3eEw1WmdSMFlKRGVfaGpNS1FpUWk4LU02VHBlOS1YY3FuakVuWnBFdVJjUVBldnZKdjF6LVQ1YkpLMDZ0VnRLc01wSU9vR3FnbXBYNktzYWxNTXg4QVdCWUoxc2VfNmRZQ2o2TVVuckpIeEUwSXhzR3pOWkJlOXlVS1dzU2h5bUhZWGRTM1RDekxfNXhycjVuVy1nbUt3MkJTZUViNm52NGFoNTh6cjZ2MVBFZ2tMTGRuaFdPbjZZQWZCMzRzMDhWMDFiUnhUNEdSNEhSOWExSVJERVFVY3c2b21POWdoeDZCTkJKN29sNnVlZnBZbzYxNmdFMXgwWEFjQlBrVUJqTmw4eTVockJsOVlGMW1MWTJ1bEZUQkctaVlOdHhIdlZOZXllYzY2NTdxWlpYcUM5WlFfQ0txT09Qc1hrdWtEdndxMjhyNXdyWjAtbXhpOWZ2Z0J1NElsZ3FpbDIwQVZtSlZJRno4c0E3cFF1VThYeVlKTHdRbllTbG1mU1NqZEtoMUtrWnlsN2pwVjJ6ZlBBV2I1RWxsSkM5QmZUXzViQ2xfbTdpWUFlRGlnTkJXZXNwaVhuLXBUOWNnc1djdUZoYm5BTlB6SGtOYzZmOWVlb3p1OUk5N194SVpKSjF6eENtQUtZck0zTTFCNE1sb1lpcWJfblJDWGExbW4wdXFBVzBqSDZ4RFFLWW5tQVQyYnpRYXdYQVhxSmd0TXlza2xVaU04MkpOR01LWGFDLUZPTEhvYWt0NENBRm9CMzg1OTJHZ1NxODV2TXRqX0d1NHVYRmRtMGFTeUZjQThSTzlVTTZPelhncUNkaTVRWjZLMlVMc0FQTEdWdDFFUmZsWlFuX19SeUhfanFfbXZKTzhHcS0wWGd1RzZkN0pLQTluSE9QaVRnUW5yeWV0OTJqTEgtVTZuMjlIZkhrUnFNQWp6eGlCTk9WQk1namd4Y2VGeEVXRVFCYk9EQk5PcFJxM29oaXZ0WlFaMFN3ajI0eWloREQ3UzRTOVg4cXhhTVBGNFhOS0M5TW1YVzkzSGhLd01ZYXJfcTZ0UERxTlJJVGV1VGZCWnN1bWJITGR1YVZnWlVLczJTQ1dEQ1lrSXpHSHVyTmY5TFNtYWppOGJhZXVBal9PTTZ3TnIzUkFTdHhadlM0bGl3RmRreFBvNFB1NE9qbDFDTDFqY05BbTlsZXBLNlJxX1QtVkREeWdkWUxsSmM4U1UtZ3JCVUFiRzJtaGpPTXF4dUd5emdXNnExLTVjelBDeUZ3TGhkaVB2MHh2TGtyNm1Id3hMbWhldVBiUzNsNVlaMENQUl9qNTVNVnFwcGFzZW5Qc2JFYnpjbWlaQlhCNF9uV3Q3bWlSUFdnbWNCcFp6MUpMNFI0N3ZyX2pIanN6SkRfbmJhRHh3c2RPakFEV0lGSTlhTUhpM3lFdUpwMzgwRlFrWmptUndwaHoxSTJfNDhXaHhqLWlJcjc1WXFlUjVaRGRDNExNUTRDU2R1cVREV1Z3dm1wY21BWXRzalJaTlFESHZlTjdYbnJBU0s2b2pBN0dvOW4ydThoWGNyS1U0Sm5uc0dod1ZRSnJlN085SDJsWG5XaGZraF9IUnB3QUJQd2xZQmJRUUFzb19YUkFaV1AzOS1oODhNUlRiaE1jaXVHdl9Yb0c3LUpkSmpWYnAtT0c1cUo2dFAtM1Z6OEtSZFdJT2Z2enUyZ3YxSmJfeEp6LXBDY2xxWjlDT1ZNRlhCNXEwRHVTTGR5S1hpa0xVZll0M1hyM0Z1N1RyeVpzMDlrR3drV09NRmFCTnJXTHlKNkJIQW1TUURjZUc2cmNhTmtRZGlCOVItNVJKWXpEWWs3SFQzdDFvZHlEUDZBdDdfVzBxcXlsak80ZlFiRzJmM1RBMjQ4bHE0dE14c0R3Q240UmRtOGowU3Uwd2lQdzRJNnlvRkZaSUh3bjJhcVljSWJ1SllyZ2xJeXZ5a1cxSzBUdlg2ZVFId2k1YTZ3MTZZOFF3S085aEFJZlV6SWtxbl9ZRUo4dGp2ZW90UXMzWFZEaGtaMnI3UkZjdTBCc2xKVEo4TFVPLXhRNEdtSjZsVlN3OUw5TjRJNjhIMzRwUU9Ta1hValNaczFYRlhJbVlQZ3pRSEhwc0gyTTVOV3NsSmlucU1oLUxLd0xsQjBpaWF6b0lMRXVFWmJjNEpuakxCa2JoU2FuUEZZU0pNUXNRc21wYmxzUXVhZVFzQUNITUJIQ3pHQ3BicE83blBBbEFpU0xpc1hlOUs1TWZScHVLVDFDQTM2Q1Vpb0xEQ0haSW9ZMEVlVDJzQWthWW1tMjR2S183cjdWSHR3NklpTUxGdzlhMXJhdVdZSl8td0tjNERYM0pubXRWRDU2M2RwNm9ta1pMSm1Xd1hQOU9BaVFKd2g1bmFvMmFkX1JQbUdTbFZpa0x5OVk3U0NKbjlUTE9jV1k0R2hxVGZzTEE2RkN0RkxLNDl0dzZCT2hZSEF3ZkJlWnNnb3cwV0hYeldENzRiUHYxWkV0Q1o0VnFoTFdzRnRaWFF2V2FMNmhLUTZaS2llcktCdTcxbnVxSE9UT2JIVXhxOGJ3Y2lDVnhXaTNJalZUZlJ0cUltSXQ3Zi1yc3BncTFzbm5QRDJGUHEwRmNrdHMyUEJIWkJMOTEwTEtKVnlsRE5uR1J5cG9zWmliRlNkVlVqeU5HYUpyVTNEZGZFTmJ2R1A1TjZFbi0yTzNqckUyWWJPY0EyNXp0eTVvblhJQXRpM2hzZlAzWE5NREFpUXY3Rno5cjZNcGdxUnUwdndUelJtSDl2ZmNJaDhRX3VPV3c2cTkxbWNWTHBzcHhSNnE3a3hZbmYzTEhTYjE4YXd2bEsyMGg0TzVFVTJNWlVkWHJNaHRIZ3hXRmFtTTNLeVh1bHM3Ym13NFhVN3ctYWxiRC1oQ2dkWTNLUjU2U3MtM3hQdUwyNUpPS29ERHdldVVJWkx2WnJwNnI5bnN3VmZIZ3h1bU1GcTFQVG0zbzl2ZmZyc0xxcmQySm9VZU5zc1lVY1R5N1QtNkZpOXgwdFB2MDlzdGkxT2JCYVVvNGRiWHRRczRTSEwzTE5aZE5yNm9SdUpUa0hHYnJtc3ctUzZ3cmtBdDE3Z2c3TmpHOU5fX09GYS1VTVlyM0tJWXdudnBHQW03YVQ5QlBkODBjbFQ1UTVUWDJPYTRNbTEtSzh2VEoyN01CUjllMjZMMUNfcnNXTE5rZE91a0ZsUHNaNXdCTU5kVXNUajhVc21JQ080c1ZQWVpnbGhEMEtZSUNyeUpXdHR4Z25TNWFzQkZPdjJWOWszWktFOFZ0VE9GckhFb0I2QTNUQy1OdE1YNHluNk9KT3Z0OEcxdE96dkU2aHdPRHV2QllFZTFtb1pDblI2OC01WFctaGRVXzRwOFFzMmtTaGgtWFh6dUdaalVJb1Z1R0NsdWQwZzdIUTZtcC1wTDhhb1ZKbnRHWjVlVHRRSDBGNmk0Q3BQVl9hMkthT3NFbzRtMVd3VjhOSVdodW1XMGR2OHpfOERrcndPTlJwUVFKQWZsRkp1enFaanRYZExLdEN3d0ZoZ3ZnMWwtVWYzYmJjc2FjODNFc0l2R2pMcW9rZVdFdzBBSHkzZkdyTGd1UlVtZGZ3MEtjQjFuaFdnLlIwTk5LazVCRlFwSXJnRUJCc0I4emc"}' headers: Accept: - application/json @@ -165,12 +218,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault4e00e7f.vault.azure.net/keys/restore?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/7cb699572d7443838ff94a8a0e79828c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lMQcqtwA-nD8Gg7Px-5JnqIJUW0OIn2XFr2X4DSJrDXwzdOClqHf4miDhUE0swSJYGUsxBYfJ8in9sqC-1iqBP_dcKvGYaEhjzgEALe14tqLRSmLDj6C6rHUEUBx1nWxOZof5DEwnlDR0ywUscQgkmnfoSjWIO8w51PgxZ_NgovDfnXRZV9oDAKSfcmqguWSPE2uXCGYIdEAiLleBgfUj47W8gTzROKmaWu2hQ-e9xrQuN8KozB8j2LZfSnNvKI6-mN6_E_Son8OFtZnOv-NHYvMmGzanj5uT6Kdg3Nb0M8HQUKov7NC0_3MJGXkWjdOAjER6QZf981xx81ddHq3BQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219221,"updated":1560219221,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault4e00e7f.vault.azure.net/keys/keybak4e00e7f/54248fddc148400a9df744075bbfe2e8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lsgBFBFSUvohGWs21xzMCFc1IRDfnH0UlZ8aJnqVozFkc9rZgaBdr5IOVmt41hxbJY2MWuJxGpS4xAZ8AmrQZ1MOmS7nUiMmorcyqZZIcZYz3P7YXLjiv-ncb9rTty7IJ2R9v111Beiq3tNdHUrQYUt1wiysQPcPk69FGq7h4B5UqDM4SHQqXF-Rm3R2fU9wct7bx3F2XqYcaMmQWZBiYMH8vbooZWyh2QziCXVB2MhfBt0gTfoLUGT4hheDyLfIgbhhky6Hi0DC-3Ecll3tlK6JHAR6V1jLxJveWPgUy2WgRMSTATWdlcYL_i9Ji9OT4dSLkCFqXWhRN8krxXf9dw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -179,7 +232,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -193,11 +246,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_crud_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_crud_operations.yaml index 744443375675..15050919b2c8 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_crud_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_crud_operations.yaml @@ -1,7 +1,6 @@ interactions: - request: - body: '{"kty": "EC-HSM", "attributes": {"enabled": true}, "tags": {"purpose": - "unit test", "test name": "CreateECKeyTest"}}' + body: null headers: Accept: - application/json @@ -10,26 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '116' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault5416109f.vault.azure.net/keys/crud-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-ec-key/062ff30e156c42b697b31031ca6fb8c5","kty":"EC-HSM","key_ops":["sign","verify"],"crv":"P-256","x":"9NhK52fVYuGoADz6b2JEdOP3Z9FhV-6n-9gOkWdWSWk","y":"1yGY4pWRTg-SuiteM_lOHgFi5eIe-UvDH35LcB4tN9A"},"attributes":{"enabled":true,"created":1560985558,"updated":1560985558,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name":"CreateECKeyTest"}}' + string: '' headers: cache-control: - no-cache content-length: - - '435' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:51 GMT expires: - '-1' pragma: @@ -38,23 +34,27 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: - body: '{"kty": "EC", "crv": "P-256"}' + body: '{"attributes": {"enabled": true}, "tags": {"purpose": "unit test", "test + name": "CreateECKeyTest"}, "kty": "EC-HSM"}' headers: Accept: - application/json @@ -63,25 +63,26 @@ interactions: Connection: - keep-alive Content-Length: - - '29' + - '116' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault5416109f.vault.azure.net/keys/crud-P-256-ec-key/create?api-version=7.0 + uri: https://vault5416109f.vault.azure.net/keys/crud-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-P-256-ec-key/8a4ceaa313fb4a2db02e1a33fbf5045f","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"3d7a4C3yzBJs4yxqfbyx0EHo38WeZ7_bSK3b4Z-Nxd4","y":"KBgQOJFuEL9nE2hBQgHhuQ_ZitqfqN4LJtsvnHUKG6c"},"attributes":{"enabled":true,"created":1560985558,"updated":1560985558,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-ec-key/7e76b0fc24ca49de8d22b283874b4aa8","kty":"EC-HSM","key_ops":["sign","verify"],"crv":"P-256","x":"FDZyxw3F8gSsjKrFwIAxsZ7UFqXykyn9dhPZqUCZ2uw","y":"uwkjuEzOUxSMEh1gm35SysHWS9wrpCUObRu3bzy5eoE"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit + test","test name":"CreateECKeyTest"}}' headers: cache-control: - no-cache content-length: - - '376' + - '435' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -95,25 +96,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"key": {"kty": "RSA", "key_ops": ["encrypt", "decrypt", "sign", "verify", - "wrapKey", "unwrapKey"], "n": "AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU", - "e": "AQAB", "d": "Ynx9JGaBSP4iUsf6ZJ6opantRNdcdmzaQrKbZg6ZQE8Ohi1FYabJWvaoPSE-CiJEsDzShXZHMhUHN4X7Bn8BXaGQhK3p9HXgiwQKmix7oAJTu4ElUIyd8UC3UWHSZr40el4PaQD-HYu_eMzCXus34MnRiNbh_BUWm6T-Eidhk9d3kNIyaSi9YNDQHW6tjWrEhhq63O7JU1j9ZonFChZxpKk20jdkQKQURVAdpOdL-5j4I70ZxFuU6wHZj8DS8oRQfwGOvZKbgYDb5jgf3UNL_7eACqq92XPVX56vm7iKbqeyjCqAIx5y3hrSRIJtZlWCwjYnYQGd4unxDLi8wmJWSQ", - "dp": "AMmhWb5yZcu6vJr8xJZ-t0_likxJRUMZAtEULaWZt2DgODj4y9JrZDJP6mvckzhQP0WXk2NuWbU2HR5pUeCN2wieG1B76VKoH76vfnaJDqT1NuJVBcP2SLHog3ffwZtMME5zjfygchG3kihqOSpwTQ9ETAqAJTkRC38fEhwAz_Cp", - "dq": "AKC9TAo9n2RDaggjdLXK8kiLrBVoaWFTpqXkzYXRhtsx4vWPAkxhfSnze05rVMl6HiXv7FnE0f0wYawzUJzoyuXBH0zS6D9BqCZPeF543AmWB27iPf38Q9Z8Rjr6oBgMSnGDV_mm8nDVQkeaDyE4cOZh-5UKvKShTKKQVwunmDNH", - "qi": "AJ_nrkLpK8BPzVeARkvSHQyKwMWZ-a8CD95qsKfn0dOZAvXY-2xhQYTEwbED-0bpTNEKbIpA-ZkaHygmnzJkNbbFAnb9pkkzU8ZQqDP3JNgMfVIroWx58Oth9nJza2j7i-MkPRCUPEq3Ao0J52z7WJIiLji8TTVYW_NaiM1oxzsH", - "p": "ANHerI1o3dLB_VLVmZZVss8VZSYN5SaeQ_0qhfOSgOFwj__waCFmy2EG7l6l6f_Z-Y0L7Mn_LNov68lyWSFa2EuQUeVj4UoFHc5Di8ZUGiSsTwFM-XMtNuv8HmGgDYLL5BIJD3eTz71LdgW-Ez38OZH34b7VeG8zfeUDb8Hi30zz", - "q": "AMPcZrZBqbc82DO8Q5zTT8ZXRGWrW36KktMllaIk1W2RHnRiQiW0jBWmcCgqUcQNHa1LwumjyNqwx28QBS37BTvG7ULGUoio6LrOeoiBGEMj-U19sX6m37plEhj5Mak7j3OPPY_T9rohjTW5aGGg9YSwq4jdz0RrmBX00ofYOjI3"}}' + body: '{"crv": "P-256", "kty": "EC"}' headers: Accept: - application/json @@ -122,25 +116,25 @@ interactions: Connection: - keep-alive Content-Length: - - '1724' + - '29' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: PUT - uri: https://vault5416109f.vault.azure.net/keys/import-test-key?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault5416109f.vault.azure.net/keys/crud-P-256-ec-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/import-test-key/24085293365e475491d115cf4b9ddd10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU","e":"AQAB"},"attributes":{"enabled":true,"created":1560985559,"updated":1560985559,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-P-256-ec-key/ccf44a9e1f0a4f65b16196c5115e7580","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"Xp3Dh254KJvTKQEI-SEno4nr47Ste9Q0OozXC1mB3os","y":"jJ2D4Cm41HnPPyqeAtcPGUAOLX7e8B2dzBLz4-HpEiw"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '664' + - '376' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -154,20 +148,25 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"kty": "RSA", "key_size": 2048, "key_ops": ["encrypt", "decrypt", "sign", - "verify", "wrapKey", "unwrapKey"], "tags": {"purpose": "unit test", "test name - ": "CreateRSAKeyTest"}}' + body: '{"key": {"n": "AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU", + "q": "AMPcZrZBqbc82DO8Q5zTT8ZXRGWrW36KktMllaIk1W2RHnRiQiW0jBWmcCgqUcQNHa1LwumjyNqwx28QBS37BTvG7ULGUoio6LrOeoiBGEMj-U19sX6m37plEhj5Mak7j3OPPY_T9rohjTW5aGGg9YSwq4jdz0RrmBX00ofYOjI3", + "p": "ANHerI1o3dLB_VLVmZZVss8VZSYN5SaeQ_0qhfOSgOFwj__waCFmy2EG7l6l6f_Z-Y0L7Mn_LNov68lyWSFa2EuQUeVj4UoFHc5Di8ZUGiSsTwFM-XMtNuv8HmGgDYLL5BIJD3eTz71LdgW-Ez38OZH34b7VeG8zfeUDb8Hi30zz", + "key_ops": ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"], + "kty": "RSA", "dq": "AKC9TAo9n2RDaggjdLXK8kiLrBVoaWFTpqXkzYXRhtsx4vWPAkxhfSnze05rVMl6HiXv7FnE0f0wYawzUJzoyuXBH0zS6D9BqCZPeF543AmWB27iPf38Q9Z8Rjr6oBgMSnGDV_mm8nDVQkeaDyE4cOZh-5UKvKShTKKQVwunmDNH", + "e": "AQAB", "dp": "AMmhWb5yZcu6vJr8xJZ-t0_likxJRUMZAtEULaWZt2DgODj4y9JrZDJP6mvckzhQP0WXk2NuWbU2HR5pUeCN2wieG1B76VKoH76vfnaJDqT1NuJVBcP2SLHog3ffwZtMME5zjfygchG3kihqOSpwTQ9ETAqAJTkRC38fEhwAz_Cp", + "qi": "AJ_nrkLpK8BPzVeARkvSHQyKwMWZ-a8CD95qsKfn0dOZAvXY-2xhQYTEwbED-0bpTNEKbIpA-ZkaHygmnzJkNbbFAnb9pkkzU8ZQqDP3JNgMfVIroWx58Oth9nJza2j7i-MkPRCUPEq3Ao0J52z7WJIiLji8TTVYW_NaiM1oxzsH", + "d": "Ynx9JGaBSP4iUsf6ZJ6opantRNdcdmzaQrKbZg6ZQE8Ohi1FYabJWvaoPSE-CiJEsDzShXZHMhUHN4X7Bn8BXaGQhK3p9HXgiwQKmix7oAJTu4ElUIyd8UC3UWHSZr40el4PaQD-HYu_eMzCXus34MnRiNbh_BUWm6T-Eidhk9d3kNIyaSi9YNDQHW6tjWrEhhq63O7JU1j9ZonFChZxpKk20jdkQKQURVAdpOdL-5j4I70ZxFuU6wHZj8DS8oRQfwGOvZKbgYDb5jgf3UNL_7eACqq92XPVX56vm7iKbqeyjCqAIx5y3hrSRIJtZlWCwjYnYQGd4unxDLi8wmJWSQ"}}' headers: Accept: - application/json @@ -176,26 +175,25 @@ interactions: Connection: - keep-alive Content-Length: - - '177' + - '1724' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: POST - uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/create?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: PUT + uri: https://vault5416109f.vault.azure.net/keys/import-test-key?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560985559,"updated":1560985559,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name ":"CreateRSAKeyTest"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/import-test-key/7123c497acc9445cb23e076358438cbe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"AKCRTQAjSsaDshtMFdW-2Ie9yVnC5Xr1Suc06PAHINd10nXkVSB-N4TO62ClCkZV3XKnqU0nHo7o95WaZpym53W_DiO62umRtFKdl4UotL2QUh0y3SZWeWuoK2u_x2aMj17rUFN0f9GZMZ0pqEQNCPRBLVJ_-TEe2nGCWSC0exxGsRqz6R1zFkB-icfzQPe4WjQELOUXQ7J9RxhAPTTHtDivYYG-BeTRHrmF04JT1_6b9T_C8bAC0i0teT-nmlBLarQtBJKATXBx1yegbPOoiTqlQrFQP4MrKWNxtnB9Tcbjcvj-Z9je0ckI_eRc4DvAhqcUh_p15Dqg4GeaoNIO_jU","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '723' + - '664' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -209,18 +207,20 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: '{"tags": {"test name ": "CreateRSAKeyTest", "purpose": "unit test"}, "key_ops": + ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"], "kty": "RSA", + "key_size": 2048}' headers: Accept: - application/json @@ -228,14 +228,18 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '177' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560985559,"updated":1560985559,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name ":"CreateRSAKeyTest"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"test + name ":"CreateRSAKeyTest","purpose":"unit test"}}' headers: cache-control: - no-cache @@ -244,7 +248,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -258,11 +262,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -278,13 +282,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 + uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560985559,"updated":1560985559,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"purpose":"unit - test","test name ":"CreateRSAKeyTest"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"test + name ":"CreateRSAKeyTest","purpose":"unit test"}}' headers: cache-control: - no-cache @@ -293,7 +297,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:58 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -307,18 +311,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"attributes": {"exp": 2524723200}, "tags": {"foo": "updated tag"}}' + body: null headers: Accept: - application/json @@ -326,27 +330,23 @@ interactions: - gzip, deflate Connection: - keep-alive - Content-Length: - - '67' - Content-Type: - - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: PATCH + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560985559,"updated":1560985560,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated - tag"}}' + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"test + name ":"CreateRSAKeyTest","purpose":"unit test"}}' headers: cache-control: - no-cache content-length: - - '706' + - '723' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:05:59 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -360,18 +360,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: '{"attributes": {"exp": 2524723200}, "tags": {"foo": "updated tag"}}' headers: Accept: - application/json @@ -380,24 +380,26 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '67' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: PATCH + uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key/?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560985560,"scheduledPurgeDate":1568761560,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560985559,"updated":1560985560,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: '{"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703473,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: - no-cache content-length: - - '841' + - '706' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:06:00 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -411,11 +413,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -430,118 +432,25 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' - headers: - cache-control: - - no-cache - content-length: - - '80' - content-type: - - application/json; charset=utf-8 - date: - - Wed, 19 Jun 2019 23:06:00 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' - headers: - cache-control: - - no-cache - content-length: - - '80' - content-type: - - application/json; charset=utf-8 - date: - - Wed, 19 Jun 2019 23:06:03 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault5416109f.vault.azure.net/keys/crud-rsa-key?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: crud-rsa-key"}}' + string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703473,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + tag"}}' headers: cache-control: - no-cache content-length: - - '80' + - '841' content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:06:07 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -555,16 +464,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -575,7 +484,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: @@ -589,7 +498,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:06:10 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -603,11 +512,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -623,12 +532,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560985560,"scheduledPurgeDate":1568761560,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560985559,"updated":1560985560,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703473,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -638,7 +547,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:06:13 GMT + - Tue, 09 Jul 2019 20:17:57 GMT expires: - '-1' pragma: @@ -652,11 +561,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -672,12 +581,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1560985560,"scheduledPurgeDate":1568761560,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/cb9cc760cdeb4438b9b0dedabec800d4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1ahTmNNGiKjh_vTYEuD36Kis8YJeAeFqfBVJgygl5R9O9pq3OSxnqumJ4Gli_j2i_AcmG_fuco5fNB_AA4Jopf8yPo2jIfmNCMT4Hs3b_3hI9gW2LryM1R5WgFu2dIZNyb9X4gKXlDWiQxAJccBFvSPuc7yrhF7W8mFU8NMsW8_XU_7A7ddw57il1-82o55xvpnT3Wz1HJMIOzbSlFSFyZvfXWhB9QRHmete8gFyhsdhjR1pihkQ0ao4gv9ChKLidyq-XOcBATqkw1329xshzfVqZJ1qmXck36Wo_54Y_Skvvm-I1PR5anHuvnp79FRHllYAjO8p03gczEHXzRUyXQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1560985559,"updated":1560985560,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: '{"recoveryId":"https://vault5416109f.vault.azure.net/deletedkeys/crud-rsa-key","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault5416109f.vault.azure.net/keys/crud-rsa-key/59a38e1f586f442b9a829c6b08e3a123","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"s0OMfLzvUEb_kRFNZUlqAvoo77gHu78NcPEIz1EybZAUoKiATABrjBA9zY-k29ruYJDsYSjeUKPK4KRriOsBFwL21WKW-bde0KAriEAwE4LarWiz2cQEZ-re0VXjReOGuLBfm_wrQIy2vEuCf7JsQYLYy5rI6yPRXtYRaB5UR7AH0i4_f9yY6tAwPNqyLp24k1pAPhBEJeysb0LgAeRlHWRBevlivGEEq7V3u36AAernUDWAIwLpajIzt3nkkBH4-zuAE2gZdZ18W4IcbUGi2A92t2oA9xwhIIRz0vuLpx6e3743oLUr6Xl0K1NallTCwWr64tys6SgwmEJxgufb5w","e":"AQAB"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703473,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -687,7 +596,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Wed, 19 Jun 2019 23:06:13 GMT + - Tue, 09 Jul 2019 20:17:57 GMT expires: - '-1' pragma: @@ -701,11 +610,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_list.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_list.yaml index c73ce799b621..51c87bd8cbb5 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_list.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_list.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaultb4dd0c0a.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:17:52 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key0/8635b396bbe94f86ab0e4874b61d5681","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"zv5okNGgF2VRKT4mzbF6tgQK6wSXaHlQP31Ez2CdyrqdGmGJky_l40tMxFwLsCZWfUS7ryVN27QD9HxcZWlvhyTwKGikddKE8cHaTlKp741Mq5g3gc-AujZMjflAc-Ww33ouQnYNhM6lBJHmOZY7hUUkbw_sygN3p3smu2wxYNR-ro19jOXdGR825soeYpOLyGhkwEyGovbF4SdwrtIQby8lBbd0UsZM0FP_TaVzqtBVADNke8vHXaiFL-KmYE_xYp6v7CJWxcgigVdZz4svev4ypfzW5vGsN6FvSF7DVMqOClppXv0BdE2pFOQFMYppcbbUCbhbMU0V_l7tQylNWQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key0/c3878937d48c43329f222ec5d2b76c1e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lJIfl9xtZMQVY21_qGFK5QXeCbwhavw_7CUAht9SAK_gIEG4AI4NIOeOi3dS0QdJTYfPUrcpySR7Bk3T87AqqBh-CKkWKIZ3nhYL6PAyycCddK4M4NWaQcwrjhi14AXZJi1qmokZIs92yvrXWy9oUh2QjatpCkL1EWtZph7UGHhWda4qU48-rL-e8BwN1LglFlR-fFTc6N_tfQHgdN0hQaM4YELOx9le2unTtPuR5acj_JTGBZwvttERTF5PBupYd3h3VbfTTecXG1EPsUelLHMRc1EaAw0q5vjwdbaLwDXfyvFEypkn8iEH_5CdYSFV0oPtwEpGi7yF4WThT6U4DQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703472,"updated":1562703472,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key1/8adb257897c64ab2b52c50a0e5ce204b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lCQwepJ-BIbUBb6PIgEX9p_OaQzPRgUAtBQJAUh_-b01Mgi8txwx81mx6uv2rCPip67wZf5PTG8axuf7qcYOn3GA2A9BBdwdgzaKyS7R5NADV5MylJFqjDGlxUQ4ALp0Yojm8IXjEd9XXCLDoccMbv_NPbhw1KyAu1D2T5o_bkXiYbNd4mDJUwt55zDpzvTQbm1z5PFn5qvwhnPS8o5VNqGcNt-G2l5RVVJ13Nkw3UB0cK3mXet9XSlZ-JjPG5kAvcIKdzDILTxtts1xkna6eyDXs0HB1kwWjTt8hNeQbLLGaB0CYAxsbTtDBSbSRTcHuYZe99azCXT6zmozUoeo3Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key1/b023d80926a5491abbdd63338ffc168b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tXG3Izs9RyCg-553a9N_Yz4N3H0AQEftAK4HNfmAM1JeEEci-F2ByaSeTs4R3jQYuQpJE1iY2YcQUop_ojFfy11CwUZnBR-wWYLj634VxuARoTDEFJTsWBACToK_D9IAsyphscqcmz3vBAnCkpnqd-Yc0UxBpLOBbv-RZfH-uuSm5xX33bU68h5ry1YDoxDK_3GEZg39SxjQu7j6T9nZmnfT1do_Ccny8TBAsViJJZga5SsjPAOwjSSw2-4ZIz2bvu65gWdck17IBulXTdatjpvdOOYoXudAHR3ZduDDkns6Id2gXuy4CHqwGCgKXpVaWd6C50w01JjGAFXuAS-TWw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -93,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key2/a8387bb284f249f1b1af3c62ff5255e1","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6VBH86nHLHM5OHZ8NPo8r18WHsCc9rQGFqmswbe8NsBn62L4sTI5hxtkDcggmcqQB4G4-HDrysmqrGEH_mL0XqKiEdSw-Mard_NLGKOwnCmmA32zEHsVjXv53Bix5ScCY6y4JS1zIakK6rPUJtrW8JFHjdFumGIgdaEKOVMw8QSSYEELTp-JvdHhsIDIEyeDmoqMup6kODkQLrgrZjGzaRb5wQBLeQyKj39UShMIxLnSLL8IQ3QgxaPOgZDDzIaBvcPyQqyST6rckEI3YQ5AkPcnCUWIrGE4wGbD_3JADHaFaGtg_sTF0sguY67z62YkmP0QBUlmWzbnrPacJq4MkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key2/37574dac891145798031bdc16ae59c2c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sG0yoon1QqM_RKNd0MAN2qmvjwSXCQ581sbogwBCNGcxX6CH2oRubUqVeQr4Nm__NGmjD4mikbKkkpxzaf9rd0-YCRpKRUL0ATCuhfIj8u0acP7uUPBrJXyqSsOUze0jvRCCCV9s3_QtqsLIxbID9uZuIu8xcJ5zkdYhtVWd2U9_601XWlboeWhWiTIMggsf5wj9PnTVYgM1tN4TY0AuFYjgb6FJD9MbJ92Y5bCDRH4Lpxze3bgskArHjOfZTVUjPd5_sCyi2a48Ky8u5N2uqEgv8OaNLML3JAeoSz07k7mg3hLnlDdldIgR0vdJPeW8MiwN1WkC2w35eRu4JGHXsQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key3/89613dac51c748d7af7fd3710ec2f314","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3Zzon-dDbt1slth5ALh8C8_zt36T5yrzIV1-PeleUl4bKodTVRayT09cR3Jzrh-VG3tXVzTwRJlf-aNjKXsTLhDnvvqCSsQ-Z7cimEsk07EAZDoZjrJOhUUevYdEWiT5v7pNUAB4b72QRjMxiS-t7NYHJn2lpOZiB0VBL2Nv2hroESZsrjWUE1QkyXx7CZg9pxI2eFhp7JQh47VX3VpspSto3jgxw8YQ0IlQeDo8Kru_76hSW-UhY3JI0GNPhnuDl4Y61ib9xYgoWOJzBr-SvDELsGa4OjlKDhQd0ffRD0sYNc7f0YhFWOc16FsHAELsVdhi7cjt030em9t-I4uRuw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key3/90b5aac48baa474f9c13e295e3d593fe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"y2AJUotY9EXCjtEq815wHGDerpkwIFgMRW-5vpt8RIhScsPc7-GK4Cog62_JwtRSduqJtEw3NWL6icZ8i-5HmRTfN-uDr7xt0iVs3sDVArc-osPBan07LTgiMPMyw_qlmwvzrdkl2LmSvipNZymHGcg-kUh7_7ZQS_ZxCAUNu9eA1zmjENAux3svJLl1ISXoVk9OwRXYeU7WdN82s8_GbkeAG6js-mazlO99z7iAM2eLKWV6rGqZgouBMfr3POBPhg895vgHDpCIX_1QQzGVwJld2mzuC81_dSDc6eTbQKEgf0Sr1-P8ywXo23DR76fYC7pzILh-LuFLnf-NbnmBpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -197,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key4/5f806f884e604096833360796f34a6c0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yitY8MIyhjS_3wBlGM0qhBW_AulYjnXlvzrHjnfKeWVYLPIbZEF3uuZUjS-_ItN4zV0I-zDe0uDWy0C9mhDMwqIaNMs5eC4jOwX3-YhfOpIqb_rWvTS3ScK-DOESk8dXC4elf_rfF0zmlU2Zr4t6aWM_pjpT1sAIJB6lv9Ux63GCGdgJujDsnb-LWINqc7wp5CpCvCI_1GvI3tC4VfVQUB9iCpLTwhtdVclKaOvw35Jp-r8MDYPQA54mAgWwY6zJ1xSH55q4Apc8bE2COgTN3ibaiRUqwXBrJ1fjAuGMMyFbCuZxCopwAp7qNYA6hK1qkHGtOgkz-OZT37iDLP4Z4w","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key4/897d30c8c9a64c5baf42ddc28cc323a2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nBnwU-Tc_DQd5xt8pxaNa1jJytxCM6nXf0TLMG-K7fURjW8FuYpaUdLEkK8PXGzxrUdJVgR-9tRLjbDgYdqxa0RG4-jHy6Rwbm3XO7gUUoQrQzlDot7HBEvrjldOKfu-evN510V-ZrH1ABJ717cm2CPtmhyW7WQ_jGxmvFq-sWwhWOUfC6UpV7AnYyBkq_fAn9vnbPRH7dXoMf1G5lCtJ5uoAlt8ys9Vr6Bq1tkf3U2JW9-SnWwfb51dZjbRkKCVuqIVm90Ik7qnUoV-r32Ehdo1t-VbKpiTfyOdhxclV8fbgCQ2NO0P1oWoC6wDEs8ut_JaX-1V6NuiocpvmSt4Tw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:56 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key5/c1fd06f3a8da4889bd4c509923c9405e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"uWnCSrJizkI1giXkRGm-nXT49ptY2BHXVoVQqQj8Xd-XCvSwJpnuxmmYLRdukgDS33kFh3N38NdHD-1o_E5tb7kkVaG31eS8egWGBuBVit-KTGE4xy33_ikMQiKPmufBkrKGJhFMgcNpmXH-fNMfjJDmq15K8LYIY8KrJPC_U9bJ1reOpBdcUpMl3S1iBBbNd_gqB9Eb8ATMLzgYUF3LADBI3_XD9JZZafMzSU0zvUtyloMSR9H8LNXEKY23tecFp8HRtzpEf3HsU4UMz8i3epwl9e0KVYQdvZivIsp5EBG0IK7nwCmStZHt4rq2IL02OAw_yeOg0-z2U8EPVMk2Kw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key5/689d4f8f09004d8aab516b94aca67d00","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"67VK0tZg5Ui7KAtcdVy4I4Y-IHca4jl966D8DWcj8ZEovCAGIWlQvFSlVRvFeEqrmoVD3zSeO5yXOQwKRzDI3rrKmc2a4b_so2cuCqxtP6OuMbiCzTS5vV57UpkqIDuN1IuyKjFnS9T_ZLAQE9y_4SLRXM9vUzzC0tCaOR924WJ-mEIfKtUlab6IytrcUHLdJi5kkO-882I-HptJfjzoyGAvHTmJIDuQWgMuMAkQP1-5ZG8V3pEinuj8FtLKcfmI8RGixEEAvTxO5FAre1aEeta2EdbsdIVTi85xXOcPu5LrWdo0URY400ytWN5yQvSDsCOEqO5Apdhk5elgl6trTw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -301,11 +354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultb4dd0c0a.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key6/32499e9118c34615a60902ed8f31bfcb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"218J__WN4DoUm2lspGnSPXqf5JfZctzFUJTR4ILEvjc8WMIXya6YT2nirUzgbq0rakULWS-EP7u4lXV7YUII-m6r3K9DgKN5klS2JejmF3Yz6wukbYsCNsbupOI8E3AjpppqEo6XNKCCpUztbS3WRfq0RHWjcYA6REjs6nG1SA2dj8nfqY3Br5ufBxQ_Bxje71Ml0CzZy5oZkJIzdBZ-lsMycHFK5_AorA1eVCcb3lrXU1eGVwEGdJZ4mjpCYLQTCsQKtMtqgraXwzYxdWJE_nz4qR45iLvLwHqBgCH664uWWPKrb9hYtWDJIwZeCWdOUPicmlBlaU9sDzsIYdnUrw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key6/a1f7acfaafc34a6495a429c6aed2eadf","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"l0gxdJyCn-voQuNg_2bqrlOXgnQStVxXHDYdvBdOlZpoq2BzzvvC73MoPcldBuI5ySRiF8B1nx82yXQKnEabXpXWvvl70Ye36l7YrDrg_Dhq38MU4Uv7i_rdqdBQPG4tr6cBz0BWR5yHqGy4LL7dcWhLtyUH3-wbC74zj2KqjCGIx3AfadEwkR8HIjCGmLhbtaUbCEU5PoHrIfLOFt1u4Ah2NripLqv54n75PeJSXaRffSzQJOU6qC4eOnHcEbP85w9SsnSlOZp4gvjqN9JvwWaoy7E20anft6zt7tjFLG_inkASqUU8QcrN9SwBkV_saE4WAWcd31-6dBpeGY9Jww","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -373,12 +426,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaultb4dd0c0a.vault.azure.net/keys?maxresults=7&api-version=7.0 + uri: https://vaultb4dd0c0a.vault.azure.net/keys?api-version=7.0&maxresults=7 response: body: - string: '{"value":[{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1560219236,"updated":1560219236,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1560219237,"updated":1560219237,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1560219238,"updated":1560219238,"recoveryLevel":"Purgeable"}}],"nextLink":null}' + string: '{"value":[{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703472,"updated":1562703472,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key4","attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key5","attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultb4dd0c0a.vault.azure.net/keys/key6","attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -387,7 +440,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:57 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -401,11 +454,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_wrap_and_unwrap.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_wrap_and_unwrap.yaml index e44ad0a872ee..f4b7259e3499 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_wrap_and_unwrap.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_key_wrap_and_unwrap.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:17:53 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIF5-XYpA4FmkDXnNBc8V626bQOC1CavGmOJz1MbK2_ZEsRqGTXGQYDCGbaEicBOBsEzwXf8qa2Kva6dwqW6MWVfsQ0XBZsJD3b-MPlC6kb1BK3Zv2ImH9y9veIaSJ2LL5hQx_D5SVmAmSRKABXLjE_xiEMdhVv4MzWs0uZFwWovxDehaCE-aBozbLAcifs5ZTubTugBcJgnZ6AFN7QhssUelXw3htWTxI-0vYz-WJdTtXBlX8r463PWMkMGSahAZSEMMOBis0RnnFxNSX6yCaMsFmrB-m_Or_wBScj7ajQixWGbicJ4DuXxVH9RCPNFfMDpl-TffgQ3sWxqgwXG0Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219170,"updated":1560219170,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ny6wNPsxof_QVouEvyn1dKEiKG8hArgSRJzUhtQKuA9A5Nij2CxUdqgezqmm4CAqyj9sBuQqIHI_BO1RKq7Jea1BzfbWmce40hhPqa1RLDG8Kb_LrYRz3GavBXCl9QR_lzn5DQYawzLCQFKvt4lXOJVhSOAiGmAayV2axTNxCM---8pwfztqf6g4dc7cUEIiLc0ZtnzcqwmgBuLu2ysrEkVRvhRY517fp5M17OLQDmIx75f9y2Nc8t_w4ZsWhIZKiUJBDfNKcBxN08Nljnowhc2CwemQ5aZLQCPZiibsOMVIi9FufjBOTBxnD2MSoaGrIuEDrLv1Y1UEK1wHE_MFuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:49 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/wrapkey?api-version=7.0 response: body: - string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d","value":"ddaeqKljniEa8SiuIiRuzDHNAGuFjDyxTD3mV7Bmi_dyvlB3MUv7ohAz-1bc0Ya8HEs70iueogoMT7xm5ZwER_X3Y4w4U2Fgh-5Hwg53U_XsD58EnJe1UszDM-hDk63aiS8UAnZ25sLU_l_3DE8E_vijXTkM0xOxBDvE0FQC4sFaqooj1zqeFqAS7kDkLwwGne1P1a48O729U7SEE_TsR0ifpZmR_Z8kIIYzKqB5dRVP5-2ax_gK_-cDI8aeN9PXlarliSzU_wbdr1vfMTkXysY0JLOh7zE3b00wHCe7DbzTaE_0Tytu0jiZ5zzQYFKWycGGVa4vNxqdFmmdIpaoRQ"}' + string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db","value":"IQvJgnnmWgtVoYpCoo_iIDfVfgYpJSe6IA-XUdpXgkQYwjzvvtQANNJtDQY3jhbWiMV71oy6uVFmDD9ey5erJrEJHpKMEivQ0FZ6H566SFd8srhiV_x3trpoFxvXgOe6osF7YCzS-DMP7GHYVRRIDf-VldIbldpS-OL7jn6ZW3SSdmKrTfHEpOyXK_78c91VVpDbf0j8QCaj_bzNWs1Xtpgxut1f0V-Yof2fX2nh7DGauR0Vk1ryopjyciU3YAozxT7CSTPalCr-EPqY48RvCCEIMf1htLaTq20mCUHVinVOSpE-cEzF2WUdmMzZXa6zvb1Ls59lO8ZhKHUSFSuVjg"}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:49 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -93,18 +146,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"alg": "RSA-OAEP", "value": "ddaeqKljniEa8SiuIiRuzDHNAGuFjDyxTD3mV7Bmi_dyvlB3MUv7ohAz-1bc0Ya8HEs70iueogoMT7xm5ZwER_X3Y4w4U2Fgh-5Hwg53U_XsD58EnJe1UszDM-hDk63aiS8UAnZ25sLU_l_3DE8E_vijXTkM0xOxBDvE0FQC4sFaqooj1zqeFqAS7kDkLwwGne1P1a48O729U7SEE_TsR0ifpZmR_Z8kIIYzKqB5dRVP5-2ax_gK_-cDI8aeN9PXlarliSzU_wbdr1vfMTkXysY0JLOh7zE3b00wHCe7DbzTaE_0Tytu0jiZ5zzQYFKWycGGVa4vNxqdFmmdIpaoRQ"}' + body: '{"alg": "RSA-OAEP", "value": "IQvJgnnmWgtVoYpCoo_iIDfVfgYpJSe6IA-XUdpXgkQYwjzvvtQANNJtDQY3jhbWiMV71oy6uVFmDD9ey5erJrEJHpKMEivQ0FZ6H566SFd8srhiV_x3trpoFxvXgOe6osF7YCzS-DMP7GHYVRRIDf-VldIbldpS-OL7jn6ZW3SSdmKrTfHEpOyXK_78c91VVpDbf0j8QCaj_bzNWs1Xtpgxut1f0V-Yof2fX2nh7DGauR0Vk1ryopjyciU3YAozxT7CSTPalCr-EPqY48RvCCEIMf1htLaTq20mCUHVinVOSpE-cEzF2WUdmMzZXa6zvb1Ls59lO8ZhKHUSFSuVjg"}' headers: Accept: - application/json @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/unwrapkey?api-version=7.0 response: body: - string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' + string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:50 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d/wrapkey?api-version=7.0 + uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db/wrapkey?api-version=7.0 response: body: - string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d","value":"B9qlb3EDd2FRWwvC6H_kHktBskceFuHeEasDoS5RJx7sfzZEak4B2l5To47Jkh7QB46g3NiQWZiGkfm-SzsNJc9NZ0FJeNJzW81kjkrovy1iDJrsvWVKeE9rogaCEHQH8TJKv4urNgmCT3fWNySo1NOnqkhAOGa7Som7N0LuRY5Pm4od_rRd9U_G2RXmCFYh9leXkCOTb83juIdDrCyQuTEegM6MMqaqikq4JwRYZf9jyusDQLtYdi9ung76AoQ5xoZr-c4R251NVm8GiXsRd_G_SYZBoLcZQus2X6AKmHnGGcobV9gcL0ZNK94fxP4fhhpbqy2zIxK7tUf2OstpDg"}' + string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db","value":"Vxzu0xm5GG276Yj9qMpc_EBbTPPlv0wuH43zSp75370UohLnX-QimTYjncIpJtcFaXDmJb2_USJUbt5GBHaBpy8MAdQWwskj4eUWLtK3b53CO3lg8aoFETRbNQqAoTp3tqihmMFhNTxTXcm5bU7e3s0VdzQ8U8_J6vAmipB6IGe-T7xDWO288DoZbubPlSylnuUu5rdfZSpzoQ-UxD77uyqpS7ll0_SpOJ8DrcrsesaCDkVDuzcCDQ5lfvh7ptdJf68faiSGIJQ_0LW7jUwv4aJzN5xf9TD27WEg9XCe6grH5x6GA38E8Yc5CsCbMlAjD_dTUyjOAFqaYUV5QdMJvw"}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:50 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -197,18 +250,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: '{"alg": "RSA-OAEP", "value": "B9qlb3EDd2FRWwvC6H_kHktBskceFuHeEasDoS5RJx7sfzZEak4B2l5To47Jkh7QB46g3NiQWZiGkfm-SzsNJc9NZ0FJeNJzW81kjkrovy1iDJrsvWVKeE9rogaCEHQH8TJKv4urNgmCT3fWNySo1NOnqkhAOGa7Som7N0LuRY5Pm4od_rRd9U_G2RXmCFYh9leXkCOTb83juIdDrCyQuTEegM6MMqaqikq4JwRYZf9jyusDQLtYdi9ung76AoQ5xoZr-c4R251NVm8GiXsRd_G_SYZBoLcZQus2X6AKmHnGGcobV9gcL0ZNK94fxP4fhhpbqy2zIxK7tUf2OstpDg"}' + body: '{"alg": "RSA-OAEP", "value": "Vxzu0xm5GG276Yj9qMpc_EBbTPPlv0wuH43zSp75370UohLnX-QimTYjncIpJtcFaXDmJb2_USJUbt5GBHaBpy8MAdQWwskj4eUWLtK3b53CO3lg8aoFETRbNQqAoTp3tqihmMFhNTxTXcm5bU7e3s0VdzQ8U8_J6vAmipB6IGe-T7xDWO288DoZbubPlSylnuUu5rdfZSpzoQ-UxD77uyqpS7ll0_SpOJ8DrcrsesaCDkVDuzcCDQ5lfvh7ptdJf68faiSGIJQ_0LW7jUwv4aJzN5xf9TD27WEg9XCe6grH5x6GA38E8Yc5CsCbMlAjD_dTUyjOAFqaYUV5QdMJvw"}' headers: Accept: - application/json @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d/unwrapkey?api-version=7.0 + uri: https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db/unwrapkey?api-version=7.0 response: body: - string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/50c3f53f43b24de28d001c8e6c60fd5d","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' + string: '{"kid":"https://vault54211096.vault.azure.net/keys/keywrap54211096/129fddeaefa2431a9c5bcfecfcd365db","value":"NTA2M2U2YWFhODQ1ZjE1MDIwMDU0Nzk0NGZkMTk5Njc5Yzk4ZWQ2Zjk5ZGEwYTBiMmRhZmVhZjFmNDY4NDQ5NmZkNTMyYzFjMjI5OTY4Y2I5ZGVlNDQ5NTdmY2VmN2NjZWY1OWNlZGEwYjM2MmU1NmJjZDc4ZmQzZmFlZTU3ODFjNjIzYzBiYjIyYjM1YmVhYmRlMDY2NGZkMzBlMGU4MjRhYmEzZGQxYjBhZmZmYzRhM2Q5NTVlZGUyMGNmNmE4NTRkNTJjZmQ"}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:12:50 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_deleted_keys.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_deleted_keys.yaml index a5d27eed4c12..235cb77d07bd 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_deleted_keys.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_deleted_keys.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null headers: Accept: - application/json @@ -9,25 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '14' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/f3b74bfc927b4610859fb7b272f736a8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q6nyE6tlP6IyR8bDxjMidQbL-nhZpgAiyfbB9Iz1g4Rj1ue9Nk5bcUBI06mxjPVuQjE9934D_le8U4rFrgAfAX4oZOFwEA8f1eBBjkdCKAB-W7-lh0pQSMeeneClknGL3zJHJq3nZNgd_bA3ZPytLjkGXMvEC9lk-MXhjREc4WhzpnTodaBF8lakPz8So9Pf3J2HQ6Z8LQGXZw5JY3yDE_y4Jfzqpr6xsvOhUHoAsTfNvVPQZ-LtJuLY62wKbG3P3cgykQwne0YlM8-ATpwsMkMbhvDQMFM889Hmi6wUmWhAXxcMpNVZS7KQEVkaT0RXhZD0V6GzToJQxwBpz027AQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560538686,"updated":1560538686,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '' headers: cache-control: - no-cache content-length: - - '659' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Fri, 14 Jun 2019 18:58:05 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -36,21 +34,24 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -65,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/78a24521ec1a40469678497c72ab453c","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"prFKS6YKIElodva6q2Lc6P5frhDp2rKIBA_p4GGdYti-lqoHmv-yUpzIJTuKVKdiGu3fJ1kvGi_cRc0igdu3xqMquhZGBkQ_LiRtCDfOJBdCl9VLowaSiPkFoVNw3-YE5s310ryQjpt0K8F4GZ3A6LOb2EIXc16F6vdyvBNEraclLV6MALB4nu-Wa_hzSHU4lv8B1pi-Lp8y8-gBkux_KrsWRwXc4TPnhzyriVsRrdGTUiMivegIfHlDtmH2rdDHKAe6nUkr0EUW_lbvg9hm4SPZh-PAU1Zxn6PzyovDlLBKcifCoUZ3n2XWb3MYM4k6mNj-NwdPbFG2Y8o7LhUL5w","e":"AQAB"},"attributes":{"enabled":true,"created":1560538686,"updated":1560538686,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/a724cd7584454a3cbe29f3a05d7eef10","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nC7idSUXK32dkqtn8M-4UB3JoMvCGygFa1TlYH0nQQUvYGn7aNp5t1vtJFqq2Jlafl5pZhdHTumt-R-Plewno2zO3iyjza1MOTdeK8FJEHPpK7I7FNH01KdYnU5Q0xY95L8tbXUcQAYW7ankxMIeKxqrokt7flCGUQeXDwOalJIyQyFvdUe4buytdGHTMFV7lgHIQx4mvekZi3yzuWgyDiuohRd8Krb0lo6suUYOIetzbcXMWqrSRmQdEO_vvptApgSOwxPrqx7OkcNsrcgJkrXQzd6xjgho57GQ6u_TxmGB7uW3LBTKnwqgWOfmELKVCH5laRfjmZk2S-Fwc1tXyQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:06 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -93,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/5a6e786838e54f968f3894e97193a63e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yXrJ3srwK1mkZc2btjuCpE3nEDXA3j1y7ZHagTjpuI9EXoftZiUIend5VBUSPgF1zKEy634ebbLyefHMUEVnU_GlgdaRt6u0fW7YMZmRLGFHOxJ5E9plPgAUYxomXYfZaA8UB9_n9eVModqRB66l3JcY98b9-eLDMaD1EeywIvoiD-bdrntgT7stV-jz4mWvA2pOFkrodAFDNrhNdILNWQuonmCrLf7EGZ6OPyoix0tFJOfiijmNdcvqy3rUr4010ADcNA6m5asRLkrYjTeGSfAS4H3qzbDaDupZw5kfcCyMkwGhLhK8hGbBk4JqubVpkeoIo2BDDZ4B_NoIDrpqWQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560538687,"updated":1560538687,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/4c4f6f8faf7b48a7bef7972fb705379d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"k8Ow-6LTG9ELTNRnQC3MqQncYGqmUY-6EgNnX1Ed5Q_SM8hJUl7dBtb8gVn0qc3KFy6Q32gBgveziuCaEaMK_wN48RCLmRLCuFsXmnHsHDdxMzEv2YL_dV0RrpcW0O0m-bgAt6z5aOOY4Pwa5EOBTyqNS5H56Rp9mm7MQijt1mcWRwOUHIRteZWpjQ2DPoSu20bufIldM97wdC7dZd38q5NFDY9W7TCNLTD__MEl-ySmkVRIEPaMpcObNtRPiB6kadylcgBsoAAR871Wzb1tdRiQkpyEko5of_9iAdZEbUY0XiG_zJpdFxfvy9rds3fXfXhSppolt-CC20IHfyjEqQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:06 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -145,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/2a626572db104246885b175a74ccc2b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"lTyJpKGn70orx3wcC4wyTK306XtEZU5og1GtmeVujfygqG5j4xZZAiDDnEgl4fqXmHCjPSjNxO_lX4e8BSIG9k1Q6R91eqt5pb2RGzLbWoflJhFUD6_aCwnQIrKOXWx4Stz0W82OQL9m73nRkowZ-V392xhx8aTbkGKfnGk8rbJ4GwjjMqhzraaSXkfp5gV9gPMLYOe2xNLun2H6Hifl7EdBMyNCOfUnz55ryCWyHoH_T0Na9HuFt4I8Wfo-D30evEwYQOYN7ZuaywQ4vav_YYEO8tWQUec6mw3lv8J6RDhwL0SSMHdhhwEuFmzOfVc3a4Zks31JR60jAIU2SzVl7w","e":"AQAB"},"attributes":{"enabled":true,"created":1560538687,"updated":1560538687,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/5c71fbc7997e4fe2b2aff141a6886d11","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nPBVYE_5WfYi6r0Z_rnhhYuBQe_qy67NeGm6dAJCSFT_4Mc2iitb5cJmEJO_0cUxMaiSWatpHzGYTzQ_c51yKjpW0CNX08FZi4ZO0JsZVUnUUL8gYjEK9VNNaeSJPvrNMHPsp-ee1TieBpdB-emZanoRtKVlotJ35-B5KYcs2BIv6KG81LI_F-ZFleqBAlR0u6ty1cggkEgz7OsKjIePcfG0wIdttqyzlKoYOVPTx6wWfHHZDcseTPWpikGUaSTi8GWL9HJ_BugI16JEW9WIALoWOlflQ7884UoH2S7WZ0wQ97MT0NbwdkwgcowiKzD6li0euweojvu2D8WYk1M-VQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:06 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -197,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/51f9dbd063c943218fad96f1bed42312","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pwWHfgnFTas5C2DEZQ4vFZiuVLni-SyXP59cu9LguA5g7m2LNzVGgm5NXuzieIqGZMstH9PbxH4SN-qbcEEfjFo2USaAv-durIyFzZn5S7EHBoICCZ9T1mknsB8mTlnilZ8GphqAUmdIpPMTirgFeAZEbzgo__l4m3mph5Gqd3MM28MjynN0EPvCLAKqFgFJnPKU_rG_T7STrf79n7iGzN5G1hFqSbScIy3FE26XRvOEpJTVsXCrOr-RbGltO_FSqf-wYLtUxvLwSo_oQ6-eUwegy4qdNzmgL59-nXhgO0dWFpNH-31j_fS0GzNkmGhBrJmnVx9njHOANSFujlalhw","e":"AQAB"},"attributes":{"enabled":true,"created":1560538687,"updated":1560538687,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/ae0bb0500b5a4899873962cedbd62ef3","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1XLrHrA2xR35sNHz7Bl23TuCTwcdOO8aY891NJLUnxQUvPxj90vLf9iQA7Kd_J9-XxU3ImXY-qp0kOb7qJPeRI2MydpRxbpipmmU1aMjDhHReaY-4eiEt1Wq4H1i73qE62VnQMY04pSkweNqcG5GANlpuBKqmaFvi6uLeK0b38ioK5leJsZ-bQyVveNa23hxZ7bAt3WlMe1-eoh0S5yITRFIlq3GDieFC546e2oL0H-TJOPsdZ6aUiF9Ff2D9mMtw1V44ulaZHs9wyQXPPcukY495_yBPq7W6NvEq4C7FJgYroBRNw1wg7y_iFQ8cvf3vTfK2m8ofwtoaeePWC75lw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703473,"updated":1562703473,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:06 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -249,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/88e100a260aa44c5937d1c6926d74b65","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rLp2mBe6FSmNhmvRGh627MHtyCaqS8ghqPxHDA_ArL3ArayPs5soyG_9pbimmMMChHrixLhv8ESxqr07BUV2Ww47af0CnxWpIyhPQpF1BmA3r3wCyECxaobPsy9eG3aBX_qLJkxIdD6F67sZdXwCXxeW-049WW9LUSLo4hVpb17oOKxSE1MRQ0Z3as4WjuE2FRUxOhBUyTnFGs-MpK88cnGEWWkcfkA1EnQ5NpUcXViSGMrMfPcakllw3bPm_hSjn9eR6vmRFU0DaN_IO7o-yWmKFZbP_GXv09g7ytGOhnACVbTi7LVSrOJmydxCME_zi5YFftCsg7o0gnUtx_T4Iw","e":"AQAB"},"attributes":{"enabled":true,"created":1560538687,"updated":1560538687,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/22e7a0ac26f541309d942aae55f56069","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"i3dwC_iG7Pnosa0C-7DGNoZ1mnH-wWPwXdYDPUr0Tl4ZQna3klAnLRO-yVO4zeB3t3kgVmQ3nYSw-M4HYM4FA-_BcbddPqcelUSWgD5LMH4SKW0sQyKPfeUWqgQQq1Q2K4LO55nLjPzu87FOdGT-fJ1hNCqCOWb6WMio6fIAA7Aw91ZfgDt1_icif_c7OzIyskD657ryxPn45P2t3z-XRUmYVg2Xt4WLSlqc-jScZtGYGNwq82E-t2IviQplaBvLB4sNtCRYzqu5NChFYUXEhZal87Q0kWTq6xZa9D-82OmwCk9WnHCia_XDf8zPNcQQsvMisoD-bDbipGZIHjLrPw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:08 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -301,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/4745e068aafd42e2bf724039273a7537","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v10K5N1Bke9rweWiGoX4AELuPp1FF5_ot1uTpk0bO1bekX_mhS-g7fM3u1nBXATe4pRH-2DVRPRC0wcotb8cQnYa94nbGiTCJ54TAddmJa2UxcoAa4U_Db_mH3rmObDrCeTOUPnd8-8d1UnuHOd68cOnSYOL7WMvvhb9UBBNBAhlNhtbBuK_wrtaWsDYRiyBHQDt2DXJmQi8Sau-IFrpgSxwEawqBncGzwB7CCXYv0AcglCeSXiYsh-Qk-gaq7PoxCOc3DfvFYcSa50AaeF7YCYwmjvxn6taOZqYtxudLWg_tH4zMpy1DYFrp6A4nzXUm-QIy_lwCxfO-LepmNr2fQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560538688,"updated":1560538688,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/39f5c626c2234116b55e89a302e37cd0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tIN4UcaySsLj1sY6wvLbFT2WCKGqf-VtnzBfNcnTNqGe0dZbX8fE4LJmPF7--uy3zN3zucHJS9TptQJuyHUI8Opb9dTnA_O7BpECmCLxdpEucmhdpX_Jn4rSZ_B2Gd6ssBO03QjwqUEiNWiIdeASyx7sCy1AazekV6jPIyiAiZH9aW9KtPp0x-hHCEnoiCTo_vsX7L0q6ZIxA1ro6pLEt6HhTFRQN3MpFU-rMMHgyqDhbgquOUzTmWaUMtMJhRvH2Fyfyd07Q3BnguhZMulT7uOWGbF3uorMcVq-TMxi3XA97ri3BQzAgDwfXxLpWxWu3cstSJwsdjs6hp-QkIFAMQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:08 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -353,18 +354,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: '{"kty": "RSA"}' headers: Accept: - application/json @@ -373,23 +374,25 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '14' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/create?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1560538688,"scheduledPurgeDate":1568314688,"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/4745e068aafd42e2bf724039273a7537","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v10K5N1Bke9rweWiGoX4AELuPp1FF5_ot1uTpk0bO1bekX_mhS-g7fM3u1nBXATe4pRH-2DVRPRC0wcotb8cQnYa94nbGiTCJ54TAddmJa2UxcoAa4U_Db_mH3rmObDrCeTOUPnd8-8d1UnuHOd68cOnSYOL7WMvvhb9UBBNBAhlNhtbBuK_wrtaWsDYRiyBHQDt2DXJmQi8Sau-IFrpgSxwEawqBncGzwB7CCXYv0AcglCeSXiYsh-Qk-gaq7PoxCOc3DfvFYcSa50AaeF7YCYwmjvxn6taOZqYtxudLWg_tH4zMpy1DYFrp6A4nzXUm-QIy_lwCxfO-LepmNr2fQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560538688,"updated":1560538688,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/38ddd3f6f70d422a8aad4175d9d01acd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rPb0on1WhFWeJd5-woJsOTbLA9Y_lFO9Mw0NtXGQ0gzwkpcvm8pSNDrudla-Dw_jKu_Yh35fL3AnsVzvttcsyNu4BrJ5lG_Nd2ApgAOtT2NqRgScCkCXheeKPtxeht7ambqKcbk7C6NBDa81zd1jqVnVojucsVAY1jkYKjw8r3WQQ53zAt2oMO4XCkEi-jQgIL4QwfY_eBfWiMZDzcYz8u8BlC07QLBHkT200YfIVDSjoKLf4pOx1hCWvzA27ndkemgt0thlL84EjLp5PE0chNtZCuLK2ojKkcSU21kZ3GV7NptS_IwyjkEvO3j9YKshOXzwFzIeMsyHMnQ5BsyRCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '793' + - '659' content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:08 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -403,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -422,118 +425,24 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: sec32f70fb3"}}' - headers: - cache-control: - - no-cache - content-length: - - '79' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 14 Jun 2019 18:58:08 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: sec32f70fb3"}}' - headers: - cache-control: - - no-cache - content-length: - - '79' - content-type: - - application/json; charset=utf-8 - date: - - Fri, 14 Jun 2019 18:58:11 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: sec32f70fb3"}}' + string: '{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1562703474,"scheduledPurgeDate":1570479474,"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/38ddd3f6f70d422a8aad4175d9d01acd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rPb0on1WhFWeJd5-woJsOTbLA9Y_lFO9Mw0NtXGQ0gzwkpcvm8pSNDrudla-Dw_jKu_Yh35fL3AnsVzvttcsyNu4BrJ5lG_Nd2ApgAOtT2NqRgScCkCXheeKPtxeht7ambqKcbk7C6NBDa81zd1jqVnVojucsVAY1jkYKjw8r3WQQ53zAt2oMO4XCkEi-jQgIL4QwfY_eBfWiMZDzcYz8u8BlC07QLBHkT200YfIVDSjoKLf4pOx1hCWvzA27ndkemgt0thlL84EjLp5PE0chNtZCuLK2ojKkcSU21kZ3GV7NptS_IwyjkEvO3j9YKshOXzwFzIeMsyHMnQ5BsyRCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '79' + - '793' content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:14 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -547,16 +456,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -567,7 +476,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 response: @@ -581,7 +490,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:17 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -595,11 +504,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -615,7 +524,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 response: @@ -629,7 +538,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:20 GMT + - Tue, 09 Jul 2019 20:17:57 GMT expires: - '-1' pragma: @@ -643,11 +552,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -663,12 +572,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1560538688,"scheduledPurgeDate":1568314688,"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/4745e068aafd42e2bf724039273a7537","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"v10K5N1Bke9rweWiGoX4AELuPp1FF5_ot1uTpk0bO1bekX_mhS-g7fM3u1nBXATe4pRH-2DVRPRC0wcotb8cQnYa94nbGiTCJ54TAddmJa2UxcoAa4U_Db_mH3rmObDrCeTOUPnd8-8d1UnuHOd68cOnSYOL7WMvvhb9UBBNBAhlNhtbBuK_wrtaWsDYRiyBHQDt2DXJmQi8Sau-IFrpgSxwEawqBncGzwB7CCXYv0AcglCeSXiYsh-Qk-gaq7PoxCOc3DfvFYcSa50AaeF7YCYwmjvxn6taOZqYtxudLWg_tH4zMpy1DYFrp6A4nzXUm-QIy_lwCxfO-LepmNr2fQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560538688,"updated":1560538688,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1562703474,"scheduledPurgeDate":1570479474,"key":{"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3/38ddd3f6f70d422a8aad4175d9d01acd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rPb0on1WhFWeJd5-woJsOTbLA9Y_lFO9Mw0NtXGQ0gzwkpcvm8pSNDrudla-Dw_jKu_Yh35fL3AnsVzvttcsyNu4BrJ5lG_Nd2ApgAOtT2NqRgScCkCXheeKPtxeht7ambqKcbk7C6NBDa81zd1jqVnVojucsVAY1jkYKjw8r3WQQ53zAt2oMO4XCkEi-jQgIL4QwfY_eBfWiMZDzcYz8u8BlC07QLBHkT200YfIVDSjoKLf4pOx1hCWvzA27ndkemgt0thlL84EjLp5PE0chNtZCuLK2ojKkcSU21kZ3GV7NptS_IwyjkEvO3j9YKshOXzwFzIeMsyHMnQ5BsyRCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -677,7 +586,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:23 GMT + - Tue, 09 Jul 2019 20:18:00 GMT expires: - '-1' pragma: @@ -691,11 +600,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -711,12 +620,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault32f70fb3.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1560538688,"scheduledPurgeDate":1568314688,"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3","attributes":{"enabled":true,"created":1560538688,"updated":1560538688,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: '{"value":[{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1562703474,"scheduledPurgeDate":1570479474,"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3","attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -725,7 +634,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:23 GMT + - Tue, 09 Jul 2019 20:18:00 GMT expires: - '-1' pragma: @@ -739,11 +648,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -759,12 +668,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.7.2 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault32f70fb3.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1560538688,"scheduledPurgeDate":1568314688,"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3","attributes":{"enabled":true,"created":1560538688,"updated":1560538688,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: '{"value":[{"recoveryId":"https://vault32f70fb3.vault.azure.net/deletedkeys/sec32f70fb3","deletedDate":1562703474,"scheduledPurgeDate":1570479474,"kid":"https://vault32f70fb3.vault.azure.net/keys/sec32f70fb3","attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -773,7 +682,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Fri, 14 Jun 2019 18:58:23 GMT + - Tue, 09 Jul 2019 20:18:00 GMT expires: - '-1' pragma: @@ -787,11 +696,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=131.107.147.26;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_versions.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_versions.yaml index 513e094e0350..506c1eec8c5b 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_versions.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_list_versions.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:17:53 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c6cefa45004d45759d9c61268d95ad15","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xEYOCyXi3mLt9Z6FipMqbauhq0_3TzeOe_4wDF_uHwUgZWWPGV9LNSARQJE-iuYYwQAhxf4FePoZQHSVuXgL1-OUOnz7CyJOo7K2mc-SnpAdvi-g_htGQpf0UP7eCx-hF2upOOkdRUtmKFdCxkMqCbX_Az0leggq-bpFZhM-4PvKXxp4yMFyr8q6NT9lz9l0HVw6HWPnQH_l6XKsmYYEedNy7gjOTaKC1THkeAhQ3v-SQJfqiMyl4HXP699ivzhB_5Jt7qrIfvmwZSa0_0-rXZTUUlrgI5n0-O105SgOB9Adkh-5CNFentIS47iBSh1Z-YnUcgMriuO0e1yggyaMAw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219221,"updated":1560219221,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/786f33cf86024134b1795f24b3f0f955","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"7koFGg8HikMmRoD_jMkk75c8jcJPc16nIpU3e3HxM8svQlFT3nM3gV2WCmkuydO98UYQIeHvm7mPNAAIpIZtnYHnDdT-YNZt4o4VEsUQvd-caLXLWaNaUkZDycCC3NLnOltKVoKNIcZz1AstY5I4o-4OYvYkTp1YeXBsHAzafCClwGbUF9bXx0N8InLGsUzNnU38Pi-RiBZQDyloHJ6x6_n5vhu2gBQUgfyCCwnknQDJD6Gw0DG0JM0lNKN3NhtMRgsuJSd0BXvty6pVN1TFvS320eNSlHIBX--hdN5U1ByKuy8MWIzSNyeF18nyW_ThlJCb-M3W4NgQN3B9hWm76w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/8c2126dfe4af4cb49de13210f872c6df","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"_BzEpMLf5-dP9P8ijS-gaehK2AlLuLXUaqTscCcOU1GLYPniv900oWkBIoPNQXI46cNgAVlr_J6WouXSUwYWxxfsoimh9g3E4ie5asFPFMr-0B1r8f6ZEZRMCMDEQ33UiO4yWdx6iBqUjZZwyoItZD6W9FZo8-3ZGKIQV3WSlahD-D1D1FRAFuJIuQjAijWa7d4pa9Uu1NeeBaKaCmXpKn66ruYuUa0qrlcf0qRyiUoPBgnD4oHG7gRtYhLOR39J8M7ntZvHPn-rs-Y5YNKVfNIaLBbGJeCg08V9lA780pUlxXr0k62lgTNmQQt2OoY8d7M90Fk5ev40wzoAwXlsvQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c2b39ea5f3ee4c1181284d3109f4f66e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"96s63BcHVYpDmfb9hVIqsWAovKP2CaBMRrOSzB4LNxD7iVCEBubsoFoc37_CqK5dhcNIRV8Z6GQoP2gDjP317V_ToJxPk6dqhSymhQGvIT6PvTaIovmzQUgl9CCn8rRC366kcZofyB4FOZVjs2UJe30EjqdyZOBraXsWWa6DCBM7uuRTXHorOcA464mz4RFSHgyoaEhQrtTwGUBUEK_4zXSnrUO7_k4tLjCgZhvNIRaC2pBQ5XY4n-jCSRw2R-5BbjSvqoVYzQ_P-6iZs2Krj9VWNzAdWTHXDr-vS5isShwo2FODdiygabZMnwtJRUv-N_QicsXe4QRii0DHPFFtLw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -93,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/e36a569904754bc39e89a5da41dc1fc1","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"m5R4TtsoGSp5K6QIoDUzEGNjwQcCkBlG195sipBaexsZcabdy516D8WNI41-IQb4QvJ_aMwbqeZqO_uN2yAHPJlbjpoRS1dsKEEvtitb4q89x4y5kuWjdAsdcLDCnEGIZncmpeESl3pGUYNSmUqVukImr6uGu4iXqIHz8OGhFdxcNBiGSVNKaYcOCWVJwrGqfkxJ47BL5uY1Aw-yKlZg0m7jmM0vKY5CIOySRZTzcYWFQHdz8lRqAZh9WN_6PrxVfwAATE0qceboqAwLj0LfHc3fVlMgeRWnevEc8LTqgfMODom1-kdeg5sh2G2jTu5wI-hvmCnL0DEEP4mfV4V51Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/8bf922cf4b0640ad8fbe53288acbcbbb","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vkJZ9m9QRSVxUOx1X1Ag2MVp3Hnl9Fs3tQ5VXXMm7d-KxntbQH2xr5KHZe3YmUK1ZHHOH_TWjneoQS0fGwkarA0GAzhGr8O_SpDDQ8l5KttNe4niAYuxRT9_5-DTOzkMAuTKyJPJS0RblaPWxo9lQLpvQLdLPD5HyQBRlz0N4g2wi5Rcdu5rqKPBFBWnNRz-CfpEPA7ov_vec5bEATmqIphxAPasKuxGGyJ4ceLWM4dnQlxsgLjI_QE4r1UoLucZPkK8_lV9W76uh28SLY4Ve-wwyq8anPYT5oOLXWrWhqrxxCHeKpFhXgpLYd3_Mong9A6OSvGmL2nG-G7LBXxfpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:41 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/cc748acfad5a49d48e8822b551c687ee","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6wiVmg7iAZEzfd19R87I0AcalkFQv-sorEXtdWIgxU_dkdzKLlo_rC6rBmqT4ZKQTjXRycT3os8eOEICSZ06ik8cwcTzq16mJssedNxHXsmJVhAGrrqiSoHLaXlLQvlrl79zPyTdbx8570KQztnhLtddyahkWQTgnk_zlAaj0lyGjsN5DefCEjIdUhDV9A2NDgQv63Y58c4okZ0IX3d_RoiWy8E92oY51KVEMbUn6hjgWq7C2oZx9k0DfQH_3U8q6v9rZldvp8o1Adv_tZCYXPkORbdKt__qwLe5ksiEMdDMfHBL0QpMy7KP5jeWLKjxePFeeSelMbLM0yUQ6cJeDQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/a0d22140c0ed4278870724e54d99b938","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"n9pVD9XL43BykxzcY1c6XKmGMuN20UbSM6uaku1GdCzvuPCWO54fdRqs8JduEvUOgRTuYV52QMGLvyb2_AXrzvedEZI6o2PE2FO5kJH8qfFyCjqiqY6H-MeFfMfa2UoVSG-ykYIxqayh_HpwSwndVMPnoc0p5_lmecZqsFCJrT5OZZTmHat9iyNXEp9G-nY4EVOPnxb_HsqxWoIivjqO_OeVUXneLYzU9iCEpihr3eXYrE8nIeiGMimehDC0EF3RlJ9BvsC2Aj6EddajS9xh8mrTXMms4k-2X_8IJwQcarPpAqiz9K7GWbCcTPdQEbddQSX5FkZ_FeeLC013JEap9Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -197,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/35b6fa9fdf6f41a28f865b3e2e19bb03","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rdHIUWpw7mtjM7lsNhbLReNUSl3pr1GukuuR_fG378Ph6n1MkNhpmmxcOHVwXWSHezZmnbsGxwCtH8ndcWmt85USl_f8lzODM4d_alBxAd-HM8OopnGlU6wQSP5wj58zN7vBEPVqVx0pfivFcVMbNJMz-eJBOvsX_AbzxzkoHAg-WxWpfogp8Q8frjQ3JBV0kk5EYr8erTA8BCx4Xhl8oBrE9v9HswEPHpUYcwucBgaPVz0P4hvXnBVJ2PR1adId_OJt1e0N7V_WUth8W9OuDomtz8r4LVAwjX5Bhiz7gMGfeeZJZRa0iA84THrjaAY7PhPzXnYT4QyhbL9NZpmvVw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/b329bb00403f4aaebbd90983b562d23d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wOKd__6i8rgLj8FY36VE4_r912xmbafiiaruIKRg0YH88kkDeFyrtuma13WFYco4C_Mpd9_VmTOBYydkLUJG3skk0pYj_lpaXgickbrUfYUO518-3rMXR6ZEfP_UAZonk6rc6rMAl1mBHPxJSI_61TKFRyDPFIVN3cfn3Rlu32MTTk4yFlGKzkx03z1DHjxNvAoDWOq4Rx7M2y-SFb3hS-Cbo9S3k3EObwpyqyLab9NuHjK8_TlaTChVSyaKegaxDKPdZSOwLpaugzKGiuYDc2Pv7FBvBysrZwWo9y8g-djQTCW7de4Sg4d1e_OgXkVd9jKM-iDmZP-0FG1KNrODUw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c4596c3e033d41bd9865a48355ea88c0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pK-wXKRFrvNDN6GFvVkrgHqhuMapdBxGVFpugv6ORf8a9xyt-amSu_DHkjfFmm_wjL5Z3X2fqjqnMhH9Ktt8VL17tnO5sqBO-tYvaHiCh_R_SnAIvRjpE-BY_WWNbX5n6pokPpqcfusTBbtfZLSoUithzUz1SyeFLzqWWlMdY8Sybj8ApRNVsSBqGffoNzk4Nhceg5MG1Chx89uR8ZDYyn9T75F2g2A10n56LBfjBscYBebjABmZc2bCuWInzEpYW8klprTwPvfCgPMs8agRHL6y6EL7_htVNPK-Q3qtLNHXrDrPO6aEiVC2t87JSDPx0Jwz9orLVhYTW7Dhud2sbw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/b948df1fd198403b9d957fe3c2b304c8","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ywQxBhO2g4PKjb28A6xkhoNpIJpbEB7CR9XnCQYugrMCTBbgn1Wu5WUIL4nFbOHQqfZFgVm3Iz3GxQx9qbEuKZAZLrQemQvPKfJxShysmvaUvfOFkWAHreHROokOOhpz6RONaOKCExDLebh6xrwY_QX4Mu3CQRLrxXAXCpNCpLkLxSGcwf2QsxyfDgDk5_do5CHWUdDIrfYeBwphry-amLVnevfaMiy-cfuEA7uNXT-g6E0G0IKpK7d4BC_-D_Ik-H1Za5lbMDYCzIDaTyILuoQ3o6AyP2sSQFu287_RMlwny17Z1z_uUSp4t33mLBmKfqnofOpj3DTFV1cSVi_58Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -301,11 +354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/adaaf10dde364e5f8a1556b5e8a17c5a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3-zCnL9oaeWXWKldTStrqwT8o-FKhe-WGY6dW9VKTMKIO5-4uKxynTFdyp2GnkjcxwdA-uYHAb9-ZLAALoUN9QCm2NUumE07mjToXVsdxXTJv5Ox8eBPwRb8KKto7xSeOAjImQSgVsaFLUJWEz--Ous6_4AgTwCIylcEnTb24AGR4t4r-LJmlq1mqpLQV-TBaJVlDJ2Up4ya0oCm55znsQD2cVVeZNFs1Mx9Tz9ZImwcY30H0uWC57lpHjDTe8GIiAH8ObFfN8iopvdinAuCxXdhbz_C6DrwSLw92yH8YXczWzpN1D_mZemZsgUg9ax_m7eCnlfLi3NaJzD1GN-SQw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219223,"updated":1560219223,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/90e449bfebae4beabf380cb73f8f9db9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"u1FSeDgoMKXed5j9AedatfVS7qA5zOZuIV5Si5_8A9H5AbJnR6SixEbxH-en_MEeFGIegUfuV_KGnGI7prxZtNH8Wcb5j3FPRsHo4icHMGXfXIJBKsGzP6pt6SzCF3PyGW9IjLFfIf1HD3LLzd5OiaYSHxtpOuHYNPzyo26O0ElTtHBwhUnp0YLSN6_gfCXLEMwjsXMk3EJ-XCDBuPXmRz4CqWgBtA8sABeU9Sy4Ptv_MsdDjLz5wRnvE-_WPHmux2-7BjMvgxln5c_vMEhkmU3L3Jaz5vEK_JO41Q3TVT09mVqudpGCu0qGzd3U4dGn_T-lEW89bR-K9Oqt52ZChQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -373,12 +426,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/versions?api-version=7.0 response: body: - string: '{"value":[{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/35b6fa9fdf6f41a28f865b3e2e19bb03","attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/8c2126dfe4af4cb49de13210f872c6df","attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/adaaf10dde364e5f8a1556b5e8a17c5a","attributes":{"enabled":true,"created":1560219223,"updated":1560219223,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c4596c3e033d41bd9865a48355ea88c0","attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c6cefa45004d45759d9c61268d95ad15","attributes":{"enabled":true,"created":1560219221,"updated":1560219221,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/cc748acfad5a49d48e8822b551c687ee","attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/e36a569904754bc39e89a5da41dc1fc1","attributes":{"enabled":true,"created":1560219222,"updated":1560219222,"recoveryLevel":"Purgeable"}}],"nextLink":null}' + string: '{"value":[{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/786f33cf86024134b1795f24b3f0f955","attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/8bf922cf4b0640ad8fbe53288acbcbbb","attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/90e449bfebae4beabf380cb73f8f9db9","attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/a0d22140c0ed4278870724e54d99b938","attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/b329bb00403f4aaebbd90983b562d23d","attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/b948df1fd198403b9d957fe3c2b304c8","attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Purgeable"}},{"kid":"https://vaultf7e00e3a.vault.azure.net/keys/testKeyf7e00e3a/c2b39ea5f3ee4c1181284d3109f4f66e","attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -387,7 +440,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -401,11 +454,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_purge.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_purge.yaml index a63bce776572..8986e7164929 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_purge.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_purge.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null headers: Accept: - application/json @@ -9,25 +9,23 @@ interactions: Connection: - keep-alive Content-Length: - - '14' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault92670ac9.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/43c4aa72dc4547faaed16ba67e4fcc75","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sq7UOlXisaHBCEbiZ1s-2vL45B0Rx2cg14h0pJlCdJ2Z-crP2xpeiOd0HY8zJSqaYa2Y2FqYMhBbAgGO8SjoKD7fwSb0e-I8bKRLqXaDhFbNgy2ZjEn_I3hPIy6xk95LdcIlKJIAsMZIm4ak9X8aeA3t7gUSAus88sBlb9cweL2i9NUQwyKgAvyA7EAtgGYjGE5n4BEIPidEXm-5GVST1Lun6746_dEavUjxdBf0uNEdKvfSLCL-7wnQVpbVl-DuaCpTfZOwHiJyLxCc9qpolEtUwggl1G4Tpf7txv8IFqpp21Uf1yaspLrvV4DnxP7iARzDAugRjLoSf_sAq6eKkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '' headers: cache-control: - no-cache content-length: - - '652' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:52 GMT expires: - '-1' pragma: @@ -36,21 +34,24 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -65,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key1/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/c8705017bf974da88cfeb12b174a0bef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2-ZUMwGyCs3V105pyumUpbGw-bfUVtaxv1h46lcMZiZgp8KIO1gNsAVfTfTIEfnAmB-2G46kaN7YSnLU4davxFZUX5FSa93sh7eZnZK1kr8F4oFvE24xsTHbONlTdT-3VB2xJlhDnl-F22nEuFvoAzV1jqSJVIb6i3OWeAFuQX4DMocSX5ZS0sy2-hyLh5vxHltuEkLbGZvSKiPgaz-t3IgT6nI5NyBeYSjiYH9lz6RJOmQTauRUGLdwpBL9cmoQnA7ltDvHOfsPzZhueYww5AbaicLdpDUHnQhtzAOGvi_7YeOOhXTOz-WpP5P8tcTYXSoE-RL5tG81hBtqU-bzQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/b7b15c069a764103aca616941247c05b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2yh-wv0OiJllHsJD2ksiZ2az_143p22PHO_3mqj_W76twCIcehvLnGrVvqBKphuOV81ZHAe0_T491wBWyagcbda_pYlYKMbhnh_U9Xm-UvQB3u6z1S7D4SotH03DCdAUFIjOad6ghlfBUJ-A-hOCOIh3QTv2vqtg7WrdUbuV_4zy1BuW0QfoBZ13apKtU-8HyalVMqkenk3__Rpqub8yifPod3Mx81JqMWGeVqKA2wwAsC8PNbu6mbjC1mGpeI1HwEZRjObRXllYLZoyN2lTOsrxZdzTQQ0biU5RL320u98tg-3a58qclvXJA8TfjEcMQyfi90fVnUBCz58A28jBUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -93,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key2/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/28a143d4e7014afa978402f83497a414","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tWIaj06I8-q63GLVDtQdzszKNBi7j6hoV9o58r1UxfvZB2v3BQGKDSUZYbK0MF96nyXYSv8aTOIxMG6yqL88wbw4QeReI6ZijIQq-uYWUBxSPrB8vhl2B2-8suXaQFgoNJL9OxoFgnmkaHSo8XVtusHjFzuE-IzdpuSCpR_Y2hMYCUovZDrC3AoIysJS_D4yl7JDlVaPdGib94b4j_2pNoPUOwAvY_cxBZjrJ29Yi4u09sc63VjV0UGsbkP9CUo3tYJEVoPFGxTT1uF0pEFoRpIyE4Aofuuf756-6L44qTS4YAZiT-QQQMrCBtWXoWMK6REYiegjD4wVmaBQ31I9-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/e2105e596e1244a496166f11ef3fcc83","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q_cGt1m0sRbgdPQvIy5YpXC4LQ49mOhVO1IH4XoTMiGgSAF5RtJzSPBWPx93I1Lmuvlml15EroYaudSZRivYgOM9BmVet_ZEAl0yL7FySYG1aepyalxZ1HU00b6Thn0MCYZawsK_HR6_VQsK4uVf0uBiwjSXgAT39yK9M6YC09ny5Tokdxi0CtlI88TBzhGVcsU4GBLB7zziUMsIdMNvU_08XXMdyFO0MZCCtZ4FKQ-LWrMXTd9TonbeyxHjKK55RYk73vOrurPdtncmL-7Tal-ReUfWNa8IBTbv2-5HF9pKeWiugPsYx5LZDkeMPShLLXLhUl9IrAWNJ75TbJnqCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -145,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key3/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/12c1cecfe3e04703baad2f0611385c85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qJ4_t6gII7YIXQnoWCKNh6noelj-5dOLk1H2nWfy21fGgRaq4S_jkv-9-wLY4eLNvXOjkVR5_Jv1BwDbkdFyZdfXP1fb9Mawx91uwbg9gQn3VXezEOwMuGJ87HMRS755WAZmC3JAnsniPu7RRTdZxP0LRMFCdfTP2QJKQeGk2CbOr5unMtKJ9YY9qRk2hwISdiRH8mLiiZSmjeRO3IGyIWrOoVCC3zpCMQCOX0ruw7vWhXgIP3gt6-U9fD4eW8D4-Lia0qh_pm-eReDPZR_MbjYF-MkyaKk1EYqPfqLwgzszcB6oIYffSTlUZWgzUs_LFnyB6kVNGwo4ID9e4rO_rQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/1d4636bd96ce41dbbca191bf2d58f491","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vUAwWk483zwp364hcyCNX8EzclzX-56CvL-UAmRkaI3rDttCvgo9lDk6unH7S1E6lB1J9ZfF5LebqqfNR7MheTPuvlq034V-eQHiMFmYgbpHkyM3YYElSQulBk7dlJ5JmVW9LgdDkVeRdKU0NXzFSbScn2gynewJx5uJDREcKRKLKbD4WXMtpUep_YgJG3tT3Moy0OrgB8GD1EeGU5xGb9HKLU6B4YxT7vOTd9oP_vrUGzWLzv2gUVcat3ey0KLymyS2UEQcXazrJ8I48plMYRFuo7rVDolp2KmqrQLYutqn5c-bW_mq0kytdH-9-NxDW_zrsVSMSjuXfa3rPXQQ1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -197,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key4/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/9a6e65b8ba3f4a06a163474704ff485b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mFq1UJkZ8EicKaSlMmB5e3Rzsvcr0C6PGNzYtv2riVPf5Gad0W-SryTNCG9hcPf5N-npfLLgjjiBBKF-7fb4IlBrCkKsPNpXgOJ9HYr793uIlE8BVRjNs4xw01mSUJ_FQuYCBqcqpwyhU3y1f2eUxh1QMBzKXdojD0CRUmV-CLiCG0R2IUrekEofpEHEkQlAO5mZP8PyJEpfZ5UjH-aXCuS3_ryLH9-Sf4uh_Xz-A7SBy-YY8C3uYuosNLa7Uj1KCX_WClEXfQwV-sqcXAeAEFo8B3sHCw8WWAGqgS1sFtC8PprlI5p5khj0i44_Lsb0CxsbgjTJRTIhGjmU_Svttw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/bf97328c64bc4190b7c0b5025152e0ef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"iC40PZjyzCPorzlWM7YTGIcZp4zP9YeUzig51DOE5iAHLXaIDbjZs2H-TO0l3q205oCX3R3dBBoJvxlEDbLjSLI4NdLym0EqIWTd4B6JelFBq-3zEeJJt4SHkMRQdkWPA7dwIEi4JgfyazFN8JkL3x-S2zSqqyzmoG4U4gpxKN3NPAyk1qmszmpKjnGzNJg7ywY9tewDlqx6MiMQkODxr27rOjXHx_hbp7iKhD3ZwTKAvX7g14JzpYIkXmKsyUtCU_pLx6HEqNUVLNyV0xfAtXctSSuudeE5LDItakUEY5oFe7iT1DlqPDOGoVmtfg3wf5OocdtajvdfZGfMRUpi4w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -249,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key5/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/5b2850df1622464fb7c130ef7c6d3413","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0_-gqTatpCgEptGxcnMRqSumMMCa7LUqn5JWG5g50AgMVqT6Fe7We2-XfnCPf-eMNx3IHXdqv0fqZY6E8ZHeKOOXJbLGNUwOXFl33R2Ofn54XMWqPeY-TbvLiG-q8bdkEvCSUzd2wQp97e_Hk0HLhpsf6Le1gBsZdIiZYrinKWtSn1-P_27UH1CIRZVdeNaRYXGGN5ffbsjcZnplKJU1nSsI7geGB2HGM5BnSvYZgWkW4KiC0_kuj_YahKg6u7hRpaQoktwm8dCttYg7oS033OTnscgUFuOwS8sl9FcbQ9aE7qikPz_CB41fLVBLFpLoQJJ9EBNjndKiA1BTUMzKoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/df257ae8ce7445499e565c897a68dfbd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3nDxOtRk2XEekU3CDix3JvDSL_ovOzFqQOiGcm4gAsnxKBF7KAOINLVdOU-jgDU1tW0lDU9EB7tgt09O3ZRn6wHfX11FbsWX_UQVVZZ1-9TN8qNm05VCT41hXVJXp0orAnc8oMj3zBgWuHRzNOO3H6HECD0O1rTtgpSzBVlkRjvJFoNZ_VeM4ALRdghGM5pZwDhZmXjPwWMNBhrxsehGcIwWwapylZUSjDTkv6RcVs7nROpDZiEyXxGkLMDZgQKcbZPUP8pUmWaWS9639d3NL-y8wvBrxkz2YLcLU4rVMZBKwQD-QA6kfRBZ-tSdm2Wjosn9tY_4ZaF5BT2bSZ4jWQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:53 GMT expires: - '-1' pragma: @@ -301,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vault92670ac9.vault.azure.net/keys/key6/create?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/dcf0577235dd49c096685fb889fc9cdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q-ykSEH79_6LDzg1JicECoqI8CAWrKqKhUq8Nqf2dZlmThZlR6xX94e7bWF5JkSMf13I2RKER22Id6Ro84r8RS5Kv5AGbtdaWcZBKHj7JYzruXLI5qlH_7Wn_A9ZtPTF38MIoFloQ_Wh8tWF7SDbnnc2B1uI17dV8FKNzRgt_G6KpFv8EGSmp60xWYSwgzdHX6jcqrG5Y_RsZXmM4q4w6Xbl9mCOE0T0oXGKfGJwk9iKu6ci6EzPox60rNapUFTGed-iGidB8ypWsqf2gobmKR1cHgjme7ww9QIGMQOzkYgP1D2vBRAtdYhvCX9ngwpx2-7bUTSYSKxtOvHhU6kSRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/1977c712eff64ef9a3384f93eec3920d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0V3D3E6vH_DcYMtkqMZjr6Cw1M6sYzYjUlu3eyNrh2h8qq2oIWFgDRzm_wvfG0k1JOuS28L_ZkYaAsIeW3mQx0liwfi0Qw3nMWFjzcLpLw9GBM6WZBr1C8uh0DbSvoWGo82FIIWONKWYZtimkep2UtBpEkbWXtBK7HMwWr1YhQxLwya-eq7R7L_F-d5CmAJCWZAfIU7shSesGIFZLYV8UzM2rfWueyEVQw0jrC5SC7eVoiIQ9t7bkGw_mooqod7KdIDIIyeaOpe-7xd8EqntmOeOl2EYehTeH2oaUwm-SWSfE53-Jf8GzqHMYLsXO0FcYTkZO2RCl630YSJWOQ0J_Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -353,18 +354,18 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: code: 200 message: OK - request: - body: null + body: '{"kty": "RSA"}' headers: Accept: - application/json @@ -373,23 +374,25 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '14' + Content-Type: + - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vault92670ac9.vault.azure.net/keys/key0?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault92670ac9.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key0","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/43c4aa72dc4547faaed16ba67e4fcc75","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sq7UOlXisaHBCEbiZ1s-2vL45B0Rx2cg14h0pJlCdJ2Z-crP2xpeiOd0HY8zJSqaYa2Y2FqYMhBbAgGO8SjoKD7fwSb0e-I8bKRLqXaDhFbNgy2ZjEn_I3hPIy6xk95LdcIlKJIAsMZIm4ak9X8aeA3t7gUSAus88sBlb9cweL2i9NUQwyKgAvyA7EAtgGYjGE5n4BEIPidEXm-5GVST1Lun6746_dEavUjxdBf0uNEdKvfSLCL-7wnQVpbVl-DuaCpTfZOwHiJyLxCc9qpolEtUwggl1G4Tpf7txv8IFqpp21Uf1yaspLrvV4DnxP7iARzDAugRjLoSf_sAq6eKkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/a46d41e3df94485998d628effc1cc9fa","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2pOHXzB_Ek3t-w1EPmq6Wv9tDkaP1tekAEn1FDJNKSMPRwsoQ77_2VE3aggrKhRAEhxxXJqCpsnSK3aOxeiO3PAnPkXBg9FqQ2GcFykqXwQaKEeJi3oFo_Ik4O3VgaGca4g3LyAIjhcU5RglkrdP6gxBPd38ykGmi5kmxBViQc2LVCSwWSK34a_QoYi_mTEu4ai3yyfn4c2DvvJA4Nc-jUoQi4XydgzaDwFIHwBpJ5VZvHbfj88iEQ5r-Ym4xsDZhPv2ywyKokNSocbNDgpk5mpzOGW4v_ZkAuS1DsMd7ZdVYtuVzg1y_jNtaK985OWrJhTz0_VWXKLoR_sBGkNN1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '779' + - '652' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -403,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -425,12 +428,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault92670ac9.vault.azure.net/keys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key3","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/12c1cecfe3e04703baad2f0611385c85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qJ4_t6gII7YIXQnoWCKNh6noelj-5dOLk1H2nWfy21fGgRaq4S_jkv-9-wLY4eLNvXOjkVR5_Jv1BwDbkdFyZdfXP1fb9Mawx91uwbg9gQn3VXezEOwMuGJ87HMRS755WAZmC3JAnsniPu7RRTdZxP0LRMFCdfTP2QJKQeGk2CbOr5unMtKJ9YY9qRk2hwISdiRH8mLiiZSmjeRO3IGyIWrOoVCC3zpCMQCOX0ruw7vWhXgIP3gt6-U9fD4eW8D4-Lia0qh_pm-eReDPZR_MbjYF-MkyaKk1EYqPfqLwgzszcB6oIYffSTlUZWgzUs_LFnyB6kVNGwo4ID9e4rO_rQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key3","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/bf97328c64bc4190b7c0b5025152e0ef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"iC40PZjyzCPorzlWM7YTGIcZp4zP9YeUzig51DOE5iAHLXaIDbjZs2H-TO0l3q205oCX3R3dBBoJvxlEDbLjSLI4NdLym0EqIWTd4B6JelFBq-3zEeJJt4SHkMRQdkWPA7dwIEi4JgfyazFN8JkL3x-S2zSqqyzmoG4U4gpxKN3NPAyk1qmszmpKjnGzNJg7ywY9tewDlqx6MiMQkODxr27rOjXHx_hbp7iKhD3ZwTKAvX7g14JzpYIkXmKsyUtCU_pLx6HEqNUVLNyV0xfAtXctSSuudeE5LDItakUEY5oFe7iT1DlqPDOGoVmtfg3wf5OocdtajvdfZGfMRUpi4w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -439,7 +442,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -453,11 +456,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -475,12 +478,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/keys/key6?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key6","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/dcf0577235dd49c096685fb889fc9cdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q-ykSEH79_6LDzg1JicECoqI8CAWrKqKhUq8Nqf2dZlmThZlR6xX94e7bWF5JkSMf13I2RKER22Id6Ro84r8RS5Kv5AGbtdaWcZBKHj7JYzruXLI5qlH_7Wn_A9ZtPTF38MIoFloQ_Wh8tWF7SDbnnc2B1uI17dV8FKNzRgt_G6KpFv8EGSmp60xWYSwgzdHX6jcqrG5Y_RsZXmM4q4w6Xbl9mCOE0T0oXGKfGJwk9iKu6ci6EzPox60rNapUFTGed-iGidB8ypWsqf2gobmKR1cHgjme7ww9QIGMQOzkYgP1D2vBRAtdYhvCX9ngwpx2-7bUTSYSKxtOvHhU6kSRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key2","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/1d4636bd96ce41dbbca191bf2d58f491","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vUAwWk483zwp364hcyCNX8EzclzX-56CvL-UAmRkaI3rDttCvgo9lDk6unH7S1E6lB1J9ZfF5LebqqfNR7MheTPuvlq034V-eQHiMFmYgbpHkyM3YYElSQulBk7dlJ5JmVW9LgdDkVeRdKU0NXzFSbScn2gynewJx5uJDREcKRKLKbD4WXMtpUep_YgJG3tT3Moy0OrgB8GD1EeGU5xGb9HKLU6B4YxT7vOTd9oP_vrUGzWLzv2gUVcat3ey0KLymyS2UEQcXazrJ8I48plMYRFuo7rVDolp2KmqrQLYutqn5c-bW_mq0kytdH-9-NxDW_zrsVSMSjuXfa3rPXQQ1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -489,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -503,11 +506,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -525,12 +528,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/keys/key5?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key5","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/5b2850df1622464fb7c130ef7c6d3413","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0_-gqTatpCgEptGxcnMRqSumMMCa7LUqn5JWG5g50AgMVqT6Fe7We2-XfnCPf-eMNx3IHXdqv0fqZY6E8ZHeKOOXJbLGNUwOXFl33R2Ofn54XMWqPeY-TbvLiG-q8bdkEvCSUzd2wQp97e_Hk0HLhpsf6Le1gBsZdIiZYrinKWtSn1-P_27UH1CIRZVdeNaRYXGGN5ffbsjcZnplKJU1nSsI7geGB2HGM5BnSvYZgWkW4KiC0_kuj_YahKg6u7hRpaQoktwm8dCttYg7oS033OTnscgUFuOwS8sl9FcbQ9aE7qikPz_CB41fLVBLFpLoQJJ9EBNjndKiA1BTUMzKoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key6","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/a46d41e3df94485998d628effc1cc9fa","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2pOHXzB_Ek3t-w1EPmq6Wv9tDkaP1tekAEn1FDJNKSMPRwsoQ77_2VE3aggrKhRAEhxxXJqCpsnSK3aOxeiO3PAnPkXBg9FqQ2GcFykqXwQaKEeJi3oFo_Ik4O3VgaGca4g3LyAIjhcU5RglkrdP6gxBPd38ykGmi5kmxBViQc2LVCSwWSK34a_QoYi_mTEu4ai3yyfn4c2DvvJA4Nc-jUoQi4XydgzaDwFIHwBpJ5VZvHbfj88iEQ5r-Ym4xsDZhPv2ywyKokNSocbNDgpk5mpzOGW4v_ZkAuS1DsMd7ZdVYtuVzg1y_jNtaK985OWrJhTz0_VWXKLoR_sBGkNN1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -539,7 +542,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -553,11 +556,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -575,12 +578,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/keys/key2?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key2","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/28a143d4e7014afa978402f83497a414","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tWIaj06I8-q63GLVDtQdzszKNBi7j6hoV9o58r1UxfvZB2v3BQGKDSUZYbK0MF96nyXYSv8aTOIxMG6yqL88wbw4QeReI6ZijIQq-uYWUBxSPrB8vhl2B2-8suXaQFgoNJL9OxoFgnmkaHSo8XVtusHjFzuE-IzdpuSCpR_Y2hMYCUovZDrC3AoIysJS_D4yl7JDlVaPdGib94b4j_2pNoPUOwAvY_cxBZjrJ29Yi4u09sc63VjV0UGsbkP9CUo3tYJEVoPFGxTT1uF0pEFoRpIyE4Aofuuf756-6L44qTS4YAZiT-QQQMrCBtWXoWMK6REYiegjD4wVmaBQ31I9-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key0","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/b7b15c069a764103aca616941247c05b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2yh-wv0OiJllHsJD2ksiZ2az_143p22PHO_3mqj_W76twCIcehvLnGrVvqBKphuOV81ZHAe0_T491wBWyagcbda_pYlYKMbhnh_U9Xm-UvQB3u6z1S7D4SotH03DCdAUFIjOad6ghlfBUJ-A-hOCOIh3QTv2vqtg7WrdUbuV_4zy1BuW0QfoBZ13apKtU-8HyalVMqkenk3__Rpqub8yifPod3Mx81JqMWGeVqKA2wwAsC8PNbu6mbjC1mGpeI1HwEZRjObRXllYLZoyN2lTOsrxZdzTQQ0biU5RL320u98tg-3a58qclvXJA8TfjEcMQyfi90fVnUBCz58A28jBUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -589,7 +592,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -603,11 +606,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -625,12 +628,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault92670ac9.vault.azure.net/keys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key4","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/9a6e65b8ba3f4a06a163474704ff485b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mFq1UJkZ8EicKaSlMmB5e3Rzsvcr0C6PGNzYtv2riVPf5Gad0W-SryTNCG9hcPf5N-npfLLgjjiBBKF-7fb4IlBrCkKsPNpXgOJ9HYr793uIlE8BVRjNs4xw01mSUJ_FQuYCBqcqpwyhU3y1f2eUxh1QMBzKXdojD0CRUmV-CLiCG0R2IUrekEofpEHEkQlAO5mZP8PyJEpfZ5UjH-aXCuS3_ryLH9-Sf4uh_Xz-A7SBy-YY8C3uYuosNLa7Uj1KCX_WClEXfQwV-sqcXAeAEFo8B3sHCw8WWAGqgS1sFtC8PprlI5p5khj0i44_Lsb0CxsbgjTJRTIhGjmU_Svttw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key4","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/df257ae8ce7445499e565c897a68dfbd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3nDxOtRk2XEekU3CDix3JvDSL_ovOzFqQOiGcm4gAsnxKBF7KAOINLVdOU-jgDU1tW0lDU9EB7tgt09O3ZRn6wHfX11FbsWX_UQVVZZ1-9TN8qNm05VCT41hXVJXp0orAnc8oMj3zBgWuHRzNOO3H6HECD0O1rTtgpSzBVlkRjvJFoNZ_VeM4ALRdghGM5pZwDhZmXjPwWMNBhrxsehGcIwWwapylZUSjDTkv6RcVs7nROpDZiEyXxGkLMDZgQKcbZPUP8pUmWaWS9639d3NL-y8wvBrxkz2YLcLU4rVMZBKwQD-QA6kfRBZ-tSdm2Wjosn9tY_4ZaF5BT2bSZ4jWQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -639,7 +642,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -653,11 +656,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -675,12 +678,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/keys/key1?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/keys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key1","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/c8705017bf974da88cfeb12b174a0bef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2-ZUMwGyCs3V105pyumUpbGw-bfUVtaxv1h46lcMZiZgp8KIO1gNsAVfTfTIEfnAmB-2G46kaN7YSnLU4davxFZUX5FSa93sh7eZnZK1kr8F4oFvE24xsTHbONlTdT-3VB2xJlhDnl-F22nEuFvoAzV1jqSJVIb6i3OWeAFuQX4DMocSX5ZS0sy2-hyLh5vxHltuEkLbGZvSKiPgaz-t3IgT6nI5NyBeYSjiYH9lz6RJOmQTauRUGLdwpBL9cmoQnA7ltDvHOfsPzZhueYww5AbaicLdpDUHnQhtzAOGvi_7YeOOhXTOz-WpP5P8tcTYXSoE-RL5tG81hBtqU-bzQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key5","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/1977c712eff64ef9a3384f93eec3920d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0V3D3E6vH_DcYMtkqMZjr6Cw1M6sYzYjUlu3eyNrh2h8qq2oIWFgDRzm_wvfG0k1JOuS28L_ZkYaAsIeW3mQx0liwfi0Qw3nMWFjzcLpLw9GBM6WZBr1C8uh0DbSvoWGo82FIIWONKWYZtimkep2UtBpEkbWXtBK7HMwWr1YhQxLwya-eq7R7L_F-d5CmAJCWZAfIU7shSesGIFZLYV8UzM2rfWueyEVQw0jrC5SC7eVoiIQ9t7bkGw_mooqod7KdIDIIyeaOpe-7xd8EqntmOeOl2EYehTeH2oaUwm-SWSfE53-Jf8GzqHMYLsXO0FcYTkZO2RCl630YSJWOQ0J_Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -689,7 +692,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:33 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -703,11 +706,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -722,118 +725,24 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' - headers: - cache-control: - - no-cache - content-length: - - '72' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 11 Jun 2019 02:13:33 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' - headers: - cache-control: - - no-cache - content-length: - - '72' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 11 Jun 2019 02:13:36 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.866 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: DELETE + uri: https://vault92670ac9.vault.azure.net/keys/key1?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key1","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/e2105e596e1244a496166f11ef3fcc83","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q_cGt1m0sRbgdPQvIy5YpXC4LQ49mOhVO1IH4XoTMiGgSAF5RtJzSPBWPx93I1Lmuvlml15EroYaudSZRivYgOM9BmVet_ZEAl0yL7FySYG1aepyalxZ1HU00b6Thn0MCYZawsK_HR6_VQsK4uVf0uBiwjSXgAT39yK9M6YC09ny5Tokdxi0CtlI88TBzhGVcsU4GBLB7zziUMsIdMNvU_08XXMdyFO0MZCCtZ4FKQ-LWrMXTd9TonbeyxHjKK55RYk73vOrurPdtncmL-7Tal-ReUfWNa8IBTbv2-5HF9pKeWiugPsYx5LZDkeMPShLLXLhUl9IrAWNJ75TbJnqCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:39 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -847,16 +756,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -867,12 +776,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key3"}}' headers: cache-control: - no-cache @@ -881,7 +790,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -895,11 +804,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -915,12 +824,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key0","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/43c4aa72dc4547faaed16ba67e4fcc75","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sq7UOlXisaHBCEbiZ1s-2vL45B0Rx2cg14h0pJlCdJ2Z-crP2xpeiOd0HY8zJSqaYa2Y2FqYMhBbAgGO8SjoKD7fwSb0e-I8bKRLqXaDhFbNgy2ZjEn_I3hPIy6xk95LdcIlKJIAsMZIm4ak9X8aeA3t7gUSAus88sBlb9cweL2i9NUQwyKgAvyA7EAtgGYjGE5n4BEIPidEXm-5GVST1Lun6746_dEavUjxdBf0uNEdKvfSLCL-7wnQVpbVl-DuaCpTfZOwHiJyLxCc9qpolEtUwggl1G4Tpf7txv8IFqpp21Uf1yaspLrvV4DnxP7iARzDAugRjLoSf_sAq6eKkQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key3","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/bf97328c64bc4190b7c0b5025152e0ef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"iC40PZjyzCPorzlWM7YTGIcZp4zP9YeUzig51DOE5iAHLXaIDbjZs2H-TO0l3q205oCX3R3dBBoJvxlEDbLjSLI4NdLym0EqIWTd4B6JelFBq-3zEeJJt4SHkMRQdkWPA7dwIEi4JgfyazFN8JkL3x-S2zSqqyzmoG4U4gpxKN3NPAyk1qmszmpKjnGzNJg7ywY9tewDlqx6MiMQkODxr27rOjXHx_hbp7iKhD3ZwTKAvX7g14JzpYIkXmKsyUtCU_pLx6HEqNUVLNyV0xfAtXctSSuudeE5LDItakUEY5oFe7iT1DlqPDOGoVmtfg3wf5OocdtajvdfZGfMRUpi4w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -929,7 +838,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -943,11 +852,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -963,12 +872,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key3?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key3","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key3/12c1cecfe3e04703baad2f0611385c85","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qJ4_t6gII7YIXQnoWCKNh6noelj-5dOLk1H2nWfy21fGgRaq4S_jkv-9-wLY4eLNvXOjkVR5_Jv1BwDbkdFyZdfXP1fb9Mawx91uwbg9gQn3VXezEOwMuGJ87HMRS755WAZmC3JAnsniPu7RRTdZxP0LRMFCdfTP2QJKQeGk2CbOr5unMtKJ9YY9qRk2hwISdiRH8mLiiZSmjeRO3IGyIWrOoVCC3zpCMQCOX0ruw7vWhXgIP3gt6-U9fD4eW8D4-Lia0qh_pm-eReDPZR_MbjYF-MkyaKk1EYqPfqLwgzszcB6oIYffSTlUZWgzUs_LFnyB6kVNGwo4ID9e4rO_rQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key2","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/1d4636bd96ce41dbbca191bf2d58f491","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vUAwWk483zwp364hcyCNX8EzclzX-56CvL-UAmRkaI3rDttCvgo9lDk6unH7S1E6lB1J9ZfF5LebqqfNR7MheTPuvlq034V-eQHiMFmYgbpHkyM3YYElSQulBk7dlJ5JmVW9LgdDkVeRdKU0NXzFSbScn2gynewJx5uJDREcKRKLKbD4WXMtpUep_YgJG3tT3Moy0OrgB8GD1EeGU5xGb9HKLU6B4YxT7vOTd9oP_vrUGzWLzv2gUVcat3ey0KLymyS2UEQcXazrJ8I48plMYRFuo7rVDolp2KmqrQLYutqn5c-bW_mq0kytdH-9-NxDW_zrsVSMSjuXfa3rPXQQ1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -977,7 +886,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -991,11 +900,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1011,12 +920,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault92670ac9.vault.azure.net/deletedkeys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key6","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/dcf0577235dd49c096685fb889fc9cdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q-ykSEH79_6LDzg1JicECoqI8CAWrKqKhUq8Nqf2dZlmThZlR6xX94e7bWF5JkSMf13I2RKER22Id6Ro84r8RS5Kv5AGbtdaWcZBKHj7JYzruXLI5qlH_7Wn_A9ZtPTF38MIoFloQ_Wh8tWF7SDbnnc2B1uI17dV8FKNzRgt_G6KpFv8EGSmp60xWYSwgzdHX6jcqrG5Y_RsZXmM4q4w6Xbl9mCOE0T0oXGKfGJwk9iKu6ci6EzPox60rNapUFTGed-iGidB8ypWsqf2gobmKR1cHgjme7ww9QIGMQOzkYgP1D2vBRAtdYhvCX9ngwpx2-7bUTSYSKxtOvHhU6kSRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key6","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key6/a46d41e3df94485998d628effc1cc9fa","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2pOHXzB_Ek3t-w1EPmq6Wv9tDkaP1tekAEn1FDJNKSMPRwsoQ77_2VE3aggrKhRAEhxxXJqCpsnSK3aOxeiO3PAnPkXBg9FqQ2GcFykqXwQaKEeJi3oFo_Ik4O3VgaGca4g3LyAIjhcU5RglkrdP6gxBPd38ykGmi5kmxBViQc2LVCSwWSK34a_QoYi_mTEu4ai3yyfn4c2DvvJA4Nc-jUoQi4XydgzaDwFIHwBpJ5VZvHbfj88iEQ5r-Ym4xsDZhPv2ywyKokNSocbNDgpk5mpzOGW4v_ZkAuS1DsMd7ZdVYtuVzg1y_jNtaK985OWrJhTz0_VWXKLoR_sBGkNN1w","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1025,7 +934,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1039,11 +948,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1059,12 +968,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key5?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key5","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/5b2850df1622464fb7c130ef7c6d3413","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0_-gqTatpCgEptGxcnMRqSumMMCa7LUqn5JWG5g50AgMVqT6Fe7We2-XfnCPf-eMNx3IHXdqv0fqZY6E8ZHeKOOXJbLGNUwOXFl33R2Ofn54XMWqPeY-TbvLiG-q8bdkEvCSUzd2wQp97e_Hk0HLhpsf6Le1gBsZdIiZYrinKWtSn1-P_27UH1CIRZVdeNaRYXGGN5ffbsjcZnplKJU1nSsI7geGB2HGM5BnSvYZgWkW4KiC0_kuj_YahKg6u7hRpaQoktwm8dCttYg7oS033OTnscgUFuOwS8sl9FcbQ9aE7qikPz_CB41fLVBLFpLoQJJ9EBNjndKiA1BTUMzKoQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key0","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key0/b7b15c069a764103aca616941247c05b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"2yh-wv0OiJllHsJD2ksiZ2az_143p22PHO_3mqj_W76twCIcehvLnGrVvqBKphuOV81ZHAe0_T491wBWyagcbda_pYlYKMbhnh_U9Xm-UvQB3u6z1S7D4SotH03DCdAUFIjOad6ghlfBUJ-A-hOCOIh3QTv2vqtg7WrdUbuV_4zy1BuW0QfoBZ13apKtU-8HyalVMqkenk3__Rpqub8yifPod3Mx81JqMWGeVqKA2wwAsC8PNbu6mbjC1mGpeI1HwEZRjObRXllYLZoyN2lTOsrxZdzTQQ0biU5RL320u98tg-3a58qclvXJA8TfjEcMQyfi90fVnUBCz58A28jBUQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1073,7 +982,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1087,11 +996,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1107,12 +1016,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key2?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key2","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key2/28a143d4e7014afa978402f83497a414","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tWIaj06I8-q63GLVDtQdzszKNBi7j6hoV9o58r1UxfvZB2v3BQGKDSUZYbK0MF96nyXYSv8aTOIxMG6yqL88wbw4QeReI6ZijIQq-uYWUBxSPrB8vhl2B2-8suXaQFgoNJL9OxoFgnmkaHSo8XVtusHjFzuE-IzdpuSCpR_Y2hMYCUovZDrC3AoIysJS_D4yl7JDlVaPdGib94b4j_2pNoPUOwAvY_cxBZjrJ29Yi4u09sc63VjV0UGsbkP9CUo3tYJEVoPFGxTT1uF0pEFoRpIyE4Aofuuf756-6L44qTS4YAZiT-QQQMrCBtWXoWMK6REYiegjD4wVmaBQ31I9-Q","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key4","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/df257ae8ce7445499e565c897a68dfbd","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3nDxOtRk2XEekU3CDix3JvDSL_ovOzFqQOiGcm4gAsnxKBF7KAOINLVdOU-jgDU1tW0lDU9EB7tgt09O3ZRn6wHfX11FbsWX_UQVVZZ1-9TN8qNm05VCT41hXVJXp0orAnc8oMj3zBgWuHRzNOO3H6HECD0O1rTtgpSzBVlkRjvJFoNZ_VeM4ALRdghGM5pZwDhZmXjPwWMNBhrxsehGcIwWwapylZUSjDTkv6RcVs7nROpDZiEyXxGkLMDZgQKcbZPUP8pUmWaWS9639d3NL-y8wvBrxkz2YLcLU4rVMZBKwQD-QA6kfRBZ-tSdm2Wjosn9tY_4ZaF5BT2bSZ4jWQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1121,7 +1030,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1135,11 +1044,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1155,12 +1064,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key4?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key4","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key4/9a6e65b8ba3f4a06a163474704ff485b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"mFq1UJkZ8EicKaSlMmB5e3Rzsvcr0C6PGNzYtv2riVPf5Gad0W-SryTNCG9hcPf5N-npfLLgjjiBBKF-7fb4IlBrCkKsPNpXgOJ9HYr793uIlE8BVRjNs4xw01mSUJ_FQuYCBqcqpwyhU3y1f2eUxh1QMBzKXdojD0CRUmV-CLiCG0R2IUrekEofpEHEkQlAO5mZP8PyJEpfZ5UjH-aXCuS3_ryLH9-Sf4uh_Xz-A7SBy-YY8C3uYuosNLa7Uj1KCX_WClEXfQwV-sqcXAeAEFo8B3sHCw8WWAGqgS1sFtC8PprlI5p5khj0i44_Lsb0CxsbgjTJRTIhGjmU_Svttw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key5","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key5/1977c712eff64ef9a3384f93eec3920d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0V3D3E6vH_DcYMtkqMZjr6Cw1M6sYzYjUlu3eyNrh2h8qq2oIWFgDRzm_wvfG0k1JOuS28L_ZkYaAsIeW3mQx0liwfi0Qw3nMWFjzcLpLw9GBM6WZBr1C8uh0DbSvoWGo82FIIWONKWYZtimkep2UtBpEkbWXtBK7HMwWr1YhQxLwya-eq7R7L_F-d5CmAJCWZAfIU7shSesGIFZLYV8UzM2rfWueyEVQw0jrC5SC7eVoiIQ9t7bkGw_mooqod7KdIDIIyeaOpe-7xd8EqntmOeOl2EYehTeH2oaUwm-SWSfE53-Jf8GzqHMYLsXO0FcYTkZO2RCl630YSJWOQ0J_Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1169,7 +1078,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1183,11 +1092,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1203,12 +1112,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault92670ac9.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key1","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/c8705017bf974da88cfeb12b174a0bef","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2-ZUMwGyCs3V105pyumUpbGw-bfUVtaxv1h46lcMZiZgp8KIO1gNsAVfTfTIEfnAmB-2G46kaN7YSnLU4davxFZUX5FSa93sh7eZnZK1kr8F4oFvE24xsTHbONlTdT-3VB2xJlhDnl-F22nEuFvoAzV1jqSJVIb6i3OWeAFuQX4DMocSX5ZS0sy2-hyLh5vxHltuEkLbGZvSKiPgaz-t3IgT6nI5NyBeYSjiYH9lz6RJOmQTauRUGLdwpBL9cmoQnA7ltDvHOfsPzZhueYww5AbaicLdpDUHnQhtzAOGvi_7YeOOhXTOz-WpP5P8tcTYXSoE-RL5tG81hBtqU-bzQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vault92670ac9.vault.azure.net/deletedkeys/key1","deletedDate":1562703475,"scheduledPurgeDate":1570479475,"key":{"kid":"https://vault92670ac9.vault.azure.net/keys/key1/e2105e596e1244a496166f11ef3fcc83","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"q_cGt1m0sRbgdPQvIy5YpXC4LQ49mOhVO1IH4XoTMiGgSAF5RtJzSPBWPx93I1Lmuvlml15EroYaudSZRivYgOM9BmVet_ZEAl0yL7FySYG1aepyalxZ1HU00b6Thn0MCYZawsK_HR6_VQsK4uVf0uBiwjSXgAT39yK9M6YC09ny5Tokdxi0CtlI88TBzhGVcsU4GBLB7zziUMsIdMNvU_08XXMdyFO0MZCCtZ4FKQ-LWrMXTd9TonbeyxHjKK55RYk73vOrurPdtncmL-7Tal-ReUfWNa8IBTbv2-5HF9pKeWiugPsYx5LZDkeMPShLLXLhUl9IrAWNJ75TbJnqCw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1217,7 +1126,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1231,11 +1140,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1253,9 +1162,9 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: string: '' @@ -1263,7 +1172,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1277,11 +1186,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1299,9 +1208,9 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key3?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: string: '' @@ -1309,7 +1218,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1323,11 +1232,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1345,7 +1254,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault92670ac9.vault.azure.net/deletedkeys/key6?api-version=7.0 response: @@ -1355,7 +1264,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1369,11 +1278,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1391,9 +1300,9 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key5?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: string: '' @@ -1401,7 +1310,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1415,11 +1324,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1437,9 +1346,9 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key2?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: string: '' @@ -1447,7 +1356,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:58 GMT expires: - '-1' pragma: @@ -1461,11 +1370,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1483,9 +1392,9 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vault92670ac9.vault.azure.net/deletedkeys/key4?api-version=7.0 + uri: https://vault92670ac9.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: string: '' @@ -1493,7 +1402,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:46 GMT + - Tue, 09 Jul 2019 20:17:59 GMT expires: - '-1' pragma: @@ -1507,11 +1416,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1529,7 +1438,7 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault92670ac9.vault.azure.net/deletedkeys/key1?api-version=7.0 response: @@ -1539,7 +1448,7 @@ interactions: cache-control: - no-cache date: - - Tue, 11 Jun 2019 02:13:47 GMT + - Tue, 09 Jul 2019 20:17:59 GMT expires: - '-1' pragma: @@ -1553,11 +1462,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_recover.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_recover.yaml index 9df65e3d8162..4ee4fb170618 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_recover.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_keys_async.test_recover.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:17:53 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -41,11 +94,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -93,11 +146,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:54 GMT expires: - '-1' pragma: @@ -145,11 +198,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -197,11 +250,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key4/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -249,11 +302,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key5/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:30 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -301,11 +354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:55 GMT expires: - '-1' pragma: @@ -353,11 +406,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -375,12 +428,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -389,7 +442,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -403,11 +456,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -425,12 +478,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key4?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -439,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -453,11 +506,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -475,12 +528,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vaulta8eb0b9c.vault.azure.net/keys/key3?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -489,7 +542,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:31 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -503,11 +556,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -525,12 +578,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key4?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -539,7 +592,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -553,11 +606,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -575,12 +628,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -589,7 +642,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -603,11 +656,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -625,12 +678,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -639,7 +692,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -653,11 +706,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -675,12 +728,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vaulta8eb0b9c.vault.azure.net/keys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5","deletedDate":1562703477,"scheduledPurgeDate":1570479477,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -689,7 +742,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -703,11 +756,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -723,7 +776,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 response: @@ -737,7 +790,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:32 GMT + - Tue, 09 Jul 2019 20:17:56 GMT expires: - '-1' pragma: @@ -751,11 +804,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -771,21 +824,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:35 GMT + - Tue, 09 Jul 2019 20:17:59 GMT expires: - '-1' pragma: @@ -799,16 +852,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -819,21 +872,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:39 GMT + - Tue, 09 Jul 2019 20:17:59 GMT expires: - '-1' pragma: @@ -847,16 +900,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -867,21 +920,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '72' + - '779' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:42 GMT + - Tue, 09 Jul 2019 20:18:00 GMT expires: - '-1' pragma: @@ -895,16 +948,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -915,12 +968,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key0"}}' + string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key1"}}' headers: cache-control: - no-cache @@ -929,7 +982,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:45 GMT + - Tue, 09 Jul 2019 20:18:00 GMT expires: - '-1' pragma: @@ -943,11 +996,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -963,21 +1016,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key1"}}' headers: cache-control: - no-cache content-length: - - '779' + - '72' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:04 GMT expires: - '-1' pragma: @@ -991,16 +1044,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 404 + message: Not Found - request: body: null headers: @@ -1011,21 +1064,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key1"}}' headers: cache-control: - no-cache content-length: - - '779' + - '72' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:07 GMT expires: - '-1' pragma: @@ -1039,16 +1092,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 404 + message: Not Found - request: body: null headers: @@ -1059,21 +1112,21 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key1"}}' headers: cache-control: - no-cache content-length: - - '779' + - '72' content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:10 GMT expires: - '-1' pragma: @@ -1087,16 +1140,16 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 404 + message: Not Found - request: body: null headers: @@ -1107,12 +1160,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1121,7 +1174,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1135,11 +1188,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1155,12 +1208,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1","deletedDate":1560219212,"scheduledPurgeDate":1567995212,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1169,7 +1222,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1183,11 +1236,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1203,12 +1256,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2","deletedDate":1562703476,"scheduledPurgeDate":1570479476,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1217,7 +1270,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1231,11 +1284,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1251,12 +1304,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5","deletedDate":1560219213,"scheduledPurgeDate":1567995213,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5","deletedDate":1562703477,"scheduledPurgeDate":1570479477,"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1265,7 +1318,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:48 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1279,11 +1332,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1301,12 +1354,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key0/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1315,7 +1368,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1329,11 +1382,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1351,12 +1404,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2/recover?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1365,7 +1418,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1379,11 +1432,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1401,12 +1454,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key3/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1415,7 +1468,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1429,11 +1482,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1451,12 +1504,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key4/recover?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1465,7 +1518,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1479,11 +1532,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1501,12 +1554,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key1/recover?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1515,7 +1568,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:13 GMT expires: - '-1' pragma: @@ -1529,11 +1582,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1551,12 +1604,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST - uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key6/recover?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key2/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1565,7 +1618,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:14 GMT expires: - '-1' pragma: @@ -1579,11 +1632,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1601,12 +1654,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vaulta8eb0b9c.vault.azure.net/deletedkeys/key5/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1615,7 +1668,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:49 GMT + - Tue, 09 Jul 2019 20:18:14 GMT expires: - '-1' pragma: @@ -1629,11 +1682,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1649,7 +1702,55 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 + response: + body: + string: '{"error":{"code":"KeyNotFound","message":"Key not found: key0"}}' + headers: + cache-control: + - no-cache + content-length: + - '64' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:18:14 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: @@ -1663,7 +1764,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:50 GMT + - Tue, 09 Jul 2019 20:18:17 GMT expires: - '-1' pragma: @@ -1677,11 +1778,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1697,7 +1798,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: @@ -1711,7 +1812,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:53 GMT + - Tue, 09 Jul 2019 20:18:20 GMT expires: - '-1' pragma: @@ -1725,11 +1826,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1745,7 +1846,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: @@ -1759,7 +1860,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:55 GMT + - Tue, 09 Jul 2019 20:18:23 GMT expires: - '-1' pragma: @@ -1773,11 +1874,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1793,7 +1894,7 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: @@ -1807,7 +1908,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:13:59 GMT + - Tue, 09 Jul 2019 20:18:26 GMT expires: - '-1' pragma: @@ -1821,11 +1922,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1841,12 +1942,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1855,7 +1956,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:02 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -1869,11 +1970,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1889,12 +1990,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1903,7 +2004,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:02 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -1917,11 +2018,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1937,12 +2038,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key3/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1951,7 +2052,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -1965,11 +2066,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -1985,12 +2086,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key4/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -1999,7 +2100,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -2013,11 +2114,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2033,12 +2134,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2047,7 +2148,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -2061,11 +2162,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2081,12 +2182,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2095,7 +2196,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -2109,11 +2210,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2129,12 +2230,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key5/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2143,7 +2244,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:29 GMT expires: - '-1' pragma: @@ -2157,11 +2258,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2177,12 +2278,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key0/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/0715914431a94d8db949d684da9d6b6d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wE0G78yV0Ka-AxxliEmi4g1cDUW1xsNp01ujxVJcLBJLCUn3qrEfTGLbu0uORLsCzj9IzldgwTQphSThE0ywzD8aelXalPI5iDDTx_BlJ25QrUkm4WHp0wDv6VVer-BJXfMkZvkilBwQz4vy6OEyUeLj-Vk7uFHq213njFbDgwOiKJxOT6y5mXv8ziv3HHWiloVuLBDv6akZsQq16RZUgMBc9b-hfvnsUE-M6CS-I0Fz-IaokKxIOJXOhv-h0GdPZ_UHDIkOODRHIlYcl-MAL_8N1GJJTo84rlncJ8IsaBlzaH13xdXFvkNEWfXGePY5wA2n4nwTYc70NSynoD_5mw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219210,"updated":1560219210,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key0/d713618fa742426aa657df5401a795e2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qwxWpjPAz7vNP67lCC5KeTu1X1zlsgZ7KXUL8ISU2HSjr6zKmrwrfY3mFv39I5uQ2PbL3ke2wMRxdSMgXS1D8uDEjVbnlbVw3cwvGI3-OvHl7uSZwUnHcb59QpoUSP11aghwrq2XpfkqryTz9woqjdNrHE8Mj9EtE_wMPCzdsLxCArBVfw5AJe1W7OqQqvghr5b1RS5o1ieKp3DQza7CSNF6soTDDf1sZbJKyGWBd5UM3mVRTw9sqk9WkVdQssGT7ajlhDmw3lfCKSLN8Sw6aub0kupmBgKUDj8vvTpi2J8s0gCnXbzi1s74rvdKGtaMLdTFTIYCvyl7YfEtrbufKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2191,7 +2292,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2205,11 +2306,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2225,12 +2326,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/89520162efd14eebb05ffcf5119bf0b5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"j2GT_3ONGVv2mDI7VVTSZaa7KiJO6SBVXg4PqSQrVuHIotLkKtPdUZbIsNJ_5uiVglndrknW8OtPGgkzFbFjPUAO9LOoAHfCLeV_cKKtoy70wtrsva_471I1nx_dGzZ4CLLJz0PcgtutATy8u4X38Xdx7wKrOXNL5JwWG2iUzomw2tGQgEpi2TZS0Lm3G6O0CLZIA-E3GiRQorqJn0pqDHvqrRZwFkb-N0WxiwVZ2lttynlspL3jySvd4obCsUtxqnEKqCo4nqzZsatEKgl2oIwkNzvmQGwHV9nGRIhRKNj6mvUlK3ewqbJdQ9EthN9FS24yk_ennrF_hL0L_D2zGQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/62da9cff34814906a43a1ac2c03bdf3d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tZMzrcsWt5PEx_oqF8vI_DDfWavSCcVWbmNLKi-5UD6CzD1pfoJeiWRbrwqW6SsMvq5sqS-HTu3ytZQSK31-yNSyubk-yMj3PpIooChe5YfJdBK9-4VQKZRydetszJzmNK6o-_7ZyqWctecGJ-OYkAa5yr3DbvK1h1rvvXtGMQHKssMbe2TeZbRAE2ukwurqnznM4eiQiLr1KCspJyNxIe20Lpdc7DR1knQ-TrPgr_-pLCb2seu8Vc3yM0E7lshqUDYQclxB-5ldsH4RkFeuIeJLhm91C1WZLT6fyuGw6OI8B0hRoqVyaqsJR9x8LQgYRTHriHYwaK6YQ1Bt4gNUuw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703474,"updated":1562703474,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2239,7 +2340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2253,11 +2354,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2273,12 +2374,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key3/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/c2d48dcdb15847ea92d5b6ae55469e41","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wvXKQNDFpJSzc_uaB4K_TJh7GmaPKC9pj7roPW_Y6JHhCzHxA-CLblggbrG7JAAu2XWTGYXgk8ysu-SKQPFxHDuhJtjQn-IiFjk9zq2J5wDpz83y9nVfqdIM0rcdVrIieWXj67WHuP8fShtv3vUDhvSiAE91YuJfGwOio8nFh87I0vGEQlHEeDtddIpZFMj5xYn5ZjBOZR1X0YywACfvpw3vHW9n9H9GirIkv_zVVA02S9M-FtOibYgs13n_vhM3ew-rcEUvaHECkg_cQd-tOjEGWv2RxkkP9lR2rGL5xyW1evDGDjdxZviK_1XaVviiYpijlO2Pub9lG_qiEQVUww","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key3/e70c04ae0ed4484887b6fc24757e5150","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yBg8bWYXhPUrkFKZitG4rD7DaAeBgKXat4nhvOJMzGZSCVsngofUDokNvwYA82ge1WCOhZs2K_H1bIbF9jlMW3Gsq9ABTVbEqk2-yf00rQ1lo5VcerfrJsOJmO2z_qVaHR10A1bW9cd0K6rbCgoQQyIs6MArbCQ8QADxQEH-yU82TQBBp_Gii3wHAE43KcJAZQdWoYFYGGDigSO4CYMAjMnsJYiDfKDhDVNE38Lu9DgGbUr3s-EnFh9aDDYTGlx2vLi38iKkPErVz0brfyIT-s_pZwxaS8Sw7lTa475BjsI6KbPCKDGVxdJQS8TXvrFkcDQ8C63XK7_-o54tMjt68Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2287,7 +2388,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2301,11 +2402,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2321,12 +2422,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key4/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/bfb3fdaf198e4856ade3caccdd061a8a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tEUBC83n8TszYpfq9mPSGODN1NsW8dH57xU_9KURnEBqzW0KMO69EZ3rPiJN2vEoKUL7Kv1c0Pf1gAmefSAOQVqJEHSabovG1H_EbpIR4L5d983FTogJ5Y1ATODQG4icRIsyeejQKRqUOXOFpgTPHdHavXryPa6EQB4vcJMOD7Q2M2cfEcJpnFn4q5J2WbjdlXoond2QUZYxAEJ1FTHjChz3uU0ZhgAy5uttzYoBJX2VB4NUw6K9Uq6eV81Aoq1J5oxsJxGWplN2CffadVPL7EpJy9TEBs-Fd9u5evxXlbcoylsaYPkV2swjxFA-9Eg8M2Gyx4PnPXMLZuieu-weBw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key4/992f93c3e10f4fdabf633dd95c4a917a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"vnHbhHBq2UBBnVgYQ9C_wuU8AptoHO-V3wA7VBOD4PY7QVtcWAQdd8g24aoXFXpYHaObfexbx7oZM11cOzIEY-T9TLLNjeB8cFmQb6WrNKlav02tR_wHWF2E6w46_9tpI6E0raZdtloJ7Xcjc8QixULEX11V1vJEOWQ3KSy7TUnQ97zJCeqFK_58zpbi6pzikWZ2ScyxA1npuhrltFyXl31setz_ClhwhNxo7X2FxjVbDsmkqeLAgZi-72i4LGxOGylte6FargfOXBm9hNpnsQPxDw7Ld717OtXcUvjnC3rKjvAq_h_IZdMkfgCbo-EGVnePOwaVrmzWnNGBgUBVrQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2335,7 +2436,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2349,11 +2450,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2369,12 +2470,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key1/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key1/ef3873817bbf4049af66f55a197062c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rOl_6uDODg5k3Ki4NZE5OWQZQt9m_bkceTqTnlhLMA7qtl4U9Me2G9F7QfB90hVORNF2Az7c9I_SOpKql1ICdV0DMoljK6H5mF639JuG0cU03wRiXvRvsaVcXJKAADkb6xw8Hv6XwYcom2ZFmYeaSjtWFJpTQpig7amBNeZHZ_RdnRqST96Cxu9xqTZYVOnpC5pMkiWY4I6MjpVix7QjHOH-LtN-DD1WbAjY7VQ-eJuv0yEAEsFGTaS0kWGWoqPHJoFV8Lp2YCXVNqtQaaSyjgjWPZPjx-DsCn5ADbpY5tSiII9flG3eyq8mLwUTupi__ENsa0EcIWf6WwsfHcKVRQ","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/d1d3778b188a4f3cb994dba9f1334357","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sjpSBeT8JHd8Wq3e2oHpR2beM63IxveF1b4FzyXKVP2d8rx4XXPZ50r7C_kmTmFRWbM8Q-_8tK5Ye9wQWzJvQbsFclSTSnhrJKV1fddRREjHI604cB67ZzqdBRSRijyINZB37_JOxtrcPmpv0NlJtcoOrrffYMhCuRhC0jEl0xvdeKYIVsDqzHzPYNcc9H8F3SlViFvhwXZsYQnwjBUT8Blz_67B_C7jVKbA1wWFStqcip5SLprYzy0iCBLiSXN6eGqvdur1AZtN0LTbG3WZ0MjvwZodozcoX1RDOrm0PBiDPrRlZkzOCtcWe2Gx-0_T6P-X-nM8U8Zsfw-eceQcpQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2383,7 +2484,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2397,11 +2498,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2417,12 +2518,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vaulta8eb0b9c.vault.azure.net/keys/key6/?api-version=7.0 + uri: https://vaulta8eb0b9c.vault.azure.net/keys/key2/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key6/58034f366e114290befb05fcb598b536","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"xZcDRh4FIA1EjoUl7YoDbCYmML6zvNyTqR_R7_l9na4dn0_gkmu5PCnKLM___rsec811qrXhEZF-u3AVuR0nBuLU3vDHGoeRaHs_8EzLhhZ0pnjYW6mwmfezC3f4VP7T6tLhlb6gDtuS3C6dTLpr4OZZLJ3MwrV4_eFFLYQbR1tiHP_A-1VV-jgS6bo96ovneUfCZyD_bdhxqDCr_PRrYLVO-0he6ZtkRRzr8RW4VS3u4j9akJNX8_riHXJTfZuyO8rMB3FBt4nJaNPxdL9EpTtvxtY7DW6aq4fX5d3qaRPr7lPHUkAlXpDqK47dRft2NIhEG8Iw2b_Gvr9Px0yVWw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219212,"updated":1560219212,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key2/787399bc262644e58a7758ee35fd2569","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r47YSKZu7Hsf8SXx9xJXqp80rqF7Wz2mxJ84DdhjPaiCJc6pZdLnCRL-IbUZiRAcp1z1oAACRSxFnsOQwZvd7AtZmc1Dq1MSYgJ_HfncdgNPBWi_XwF--9wTMVNOFDNnF3-6UrIGUU_bmaqPdi5B7YNK_xFQT7MOnF2t6Q6pV_DS9b86PUO_DTzE_Ms0tTLL-UWkojLoy19XeREZs9qx8bc0i611-3HgnAiDgkH853eI8_TOMpTYrvuH7MVtMy78aULmWYD9UHe5a1LbPwOuZAj3lAZ5N08xCBInCJ2_wTwXfCFlcoPN1IHHC0SntUgLyjOr1XbmFkUVc9VoLsYIpw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703475,"updated":1562703475,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2431,7 +2532,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2445,11 +2546,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: @@ -2465,12 +2566,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/0.0.1 azsdk-python-azure-keyvault/7.0 + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vaulta8eb0b9c.vault.azure.net/keys/key5/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/d686f8e8efe34506b76bafffcb3eecdc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3weuTHtCL7OQFfs2M_5oTo28W1aIZxXsnFFRkxRydDVKdantCzKNO1hyJppEx1nvmmwrt5DxpsdYltJl-lHDOQfrHyG9cwkmiAijuLc6kZx2m_MvTovYYMLzWBXSz9BJ8apMMdhpaVosE7OuoyWAtqlZINAuDwFgJ3J4RLXbOIMWr5DvmJu-Lu44Gnl_6E3vNNkqEb3SuYhoYfNhm6WBh7JuB8Ut0NzLTTtRdGmBpaSdQZeUws8JSqo-bJmgGv1qBMzKN1eCZCPiDZXL5XyseKkh-LrOlCSQiObllJmniNBq3n8Q5Z4Q2F7RWpM2pRPwrkuM1h-arUvHLb3I3mKUHw","e":"AQAB"},"attributes":{"enabled":true,"created":1560219211,"updated":1560219211,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaulta8eb0b9c.vault.azure.net/keys/key5/0d6f3f79950b4e968fa21e9307ae0bb2","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tFzbDseA9Ctm1Ohe9vAH5QvKgM1Ngw0EpF3Je_2pgj_PwmQ5fe4ojese6cOKYpMYlyqgWfMop0__K81iFE-27R6HGZjb8dPXjFK6hWr0zdIYI0TvCiAXe_9BaegT6zkA1wW6neuYJkmuwOx4JpfSDfJmILCZPj3pqWubFtl4tfwhrXXZFzCSkgRV3EqbW9bnFwmTlUFpL5EzbuA0R9iOZwOR4NwbV8VazDMkF7TKXcw4OlE5hMRtwJw9Pdg8erW9Ng2GFzlGQF3Tzh-i8g7a98KIsHCnMSMDB3oee5EGDmpg--i1w_k3KmBmhV6V2JlT3AmEytUnsbMw-LywvxYakw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703476,"updated":1562703476,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -2479,7 +2580,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 11 Jun 2019 02:14:03 GMT + - Tue, 09 Jul 2019 20:18:30 GMT expires: - '-1' pragma: @@ -2493,11 +2594,11 @@ interactions: x-content-type-options: - nosniff x-ms-keyvault-network-info: - - addr=76.121.58.221;act_addr_fam=InterNetwork; + - addr=131.107.160.58;act_addr_fam=InterNetwork; x-ms-keyvault-region: - westus x-ms-keyvault-service-version: - - 1.1.0.866 + - 1.1.0.872 x-powered-by: - ASP.NET status: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_crud_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_crud_operations.yaml index 2e23fdad5478..9258d94c5ff9 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_crud_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_crud_operations.yaml @@ -1,6 +1,59 @@ interactions: - request: - body: '{"attributes": {"exp": 2527401600}, "kty": "RSA-HSM"}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault105514c1.vault.azure.net/keys/key-name/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:23:02 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: !!python/unicode '{"attributes": {"exp": 2527401600}, "kty": "RSA-HSM"}' headers: Accept: - application/json @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault105514c1.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/79b2c81aceef4a15a4247e7b5c352b56","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ygJwrLIxx7nMX2ksu_6qb9O9ZHxcm-Ht-MP4b3s1KRbmhlge4ZJkqxuYF39LqHu1xDDgf_logTeobIKhJAgru-T7PW3B8tf1Kf11pgsTOchQp8oHpKKBID_P-bc-dnFAgyqhV6l8HylMJPzEphWPLemodlmKFFDNfoKiQdus_Gjmzn3TDNsABJDp2ayGpMgDPiiBGpp3uSuMHN5ixn6TNYm3r5JUmSXc-DbpzdfE7CKRj5vGXX8dn5n-FCLFsGAyT1dzC_psk_hLPr51LUSKsX2B5go6l7AMQBXrQlGjbdnc0OIFcX8e6DamrN3UfcoY3yd6i6ox7oJad-bA6Tu2kQ","e":"AAEAAQ"},"attributes":{"enabled":true,"exp":2527401600,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/922b73d9d291479383d14e7bdcecae4c","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ztBPcqss3BRON3GNwJ5GIPAnZLqVS4R0CRepNYTIPgSWwjcOPQTBcGpnerWHIML2zQs4TEc21RlybOuMJTXOI8qoPmEb6QIZewVGjpoMilmbRLeg4AQRF1eWMZJw2VoWRU5KlZsNo7ybPnpGGf9grYD_N-I4PaYRXRJvwNvlXAauEZ8_UiwJ_2-6_W9ixhWhl6UUcNn2nRKvBEtXxDinSpIjfy6oUcLdgGaAdwkITG_1L8HR8LMilTFwdVhPr9I5Ve0HICSzvCrRdWYGmL6OILWDMARU6mevKPtRFLa0RMh3_y6uSLr4O3KlvBhN4PYvCLjCnj6pAzlrPWb8XvSkmQ","e":"AAEAAQ"},"attributes":{"enabled":true,"exp":2527401600,"created":1562703784,"updated":1562703784,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:23:03 GMT expires: - '-1' pragma: @@ -52,8 +105,8 @@ interactions: code: 200 message: OK - request: - body: '{"key_ops": ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"], - "kty": "RSA-HSM", "key_size": 2048}' + body: !!python/unicode '{"key_ops": ["encrypt", "decrypt", "sign", "verify", "wrapKey", + "unwrapKey"], "kty": "RSA-HSM", "key_size": 2048}' headers: Accept: - application/json @@ -66,12 +119,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault105514c1.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/a564297efd3642018b6cf7d17f6aab70","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tKmZ75yphSGyWirlurGinKg0t2Ty9GLw4jHmZDGvW1PP-bOVj7lhz7hA4j1jnGZjJ_LWcg9cmy9j_vdYsTt327-lnwZAWaPRX8jBfseNJHhlI8otMzBUfVzmbDrOskVSgZfkVZ8bezun1tIMLkF8xOppIVwFl42pdwSPsOKrXUm-pkxVqprn1BhCsgzu-CtgQp3DldIZTyzsYZ5gstGvOvYx8JLVniWzAeUkcoFIUJmR073ZGqincfnJfAT2v8i7ERqRH4bLEqEMBWLgkYSJUox6U7rrBEEADr6LIaDOP27l5k1mMHHIMMTo6e1f-oVxcPW2mBQBm-JKwTc2vXoAeQ","e":"AAEAAQ"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/ccab8d0b8504440fb72c44a2414341a5","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rLfgAs-1Da-OHVNbjusBMX_tXkF93Fe7ewolE6ufslJuqbGQQOz58iA9rHnuX84Ni8vXLPLFCIMOmC4fmjxuccp3lBloY3sn_dScujw_4_zjJICZHwfruqy3baduUBrbDDo1fUKo-cWfoaIVLGql-ZpnPuBoKpN7jC6tjkpknN1Zc9MP1LAw-vGdChGKFY83Z1ids84kaZaRGoEMAg7nZ1_rZxmvenyNqCnvBOlQsHYyu8xye7NbbLDYQUZiWqV_LQJZYwqh-q8fH9rjlryaX0rEO6XfTqOoJ70BTEqLKY8hlLYtYjr-ihUIoG1gKR6HkBv0t4ArDqoi5ZvXxY2gTw","e":"AAEAAQ"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -80,7 +133,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -105,7 +158,7 @@ interactions: code: 200 message: OK - request: - body: '{"crv": "P-256", "kty": "EC"}' + body: !!python/unicode '{"crv": "P-256", "kty": "EC"}' headers: Accept: - application/json @@ -118,12 +171,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault105514c1.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"jdeRSacsi52xR6FvUirBwoW3ggJkbORU7uzCqnzmzyw","y":"7RT7S9dqt25zA5VCtZ4TZmbxTGFw5HJ3iOJZb1bX4mE"},"attributes":{"enabled":true,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lEVPrfzNNzl-Z0_tSd5Sm9MsBCL9gsYPvd2ZsMod2hM","y":"X7bP7WwVbbCLq4GFySMGhEX-KnlkMY1Sxq6urnVBa7k"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -132,7 +185,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -166,12 +219,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault105514c1.vault.azure.net/keys/key-name/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"jdeRSacsi52xR6FvUirBwoW3ggJkbORU7uzCqnzmzyw","y":"7RT7S9dqt25zA5VCtZ4TZmbxTGFw5HJ3iOJZb1bX4mE"},"attributes":{"enabled":true,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lEVPrfzNNzl-Z0_tSd5Sm9MsBCL9gsYPvd2ZsMod2hM","y":"X7bP7WwVbbCLq4GFySMGhEX-KnlkMY1Sxq6urnVBa7k"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -180,7 +233,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -214,12 +267,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe?api-version=7.0 + uri: https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"jdeRSacsi52xR6FvUirBwoW3ggJkbORU7uzCqnzmzyw","y":"7RT7S9dqt25zA5VCtZ4TZmbxTGFw5HJ3iOJZb1bX4mE"},"attributes":{"enabled":true,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lEVPrfzNNzl-Z0_tSd5Sm9MsBCL9gsYPvd2ZsMod2hM","y":"X7bP7WwVbbCLq4GFySMGhEX-KnlkMY1Sxq6urnVBa7k"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -228,7 +281,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -253,7 +306,8 @@ interactions: code: 200 message: OK - request: - body: '{"attributes": {"exp": 2524723200}, "tags": {"foo": "updated tag"}}' + body: !!python/unicode '{"attributes": {"exp": 2524723200}, "tags": {"foo": "updated + tag"}}' headers: Accept: - application/json @@ -266,12 +320,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: PATCH uri: https://vault105514c1.vault.azure.net/keys/key-name/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"jdeRSacsi52xR6FvUirBwoW3ggJkbORU7uzCqnzmzyw","y":"7RT7S9dqt25zA5VCtZ4TZmbxTGFw5HJ3iOJZb1bX4mE"},"attributes":{"enabled":true,"exp":2524723200,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: !!python/unicode '{"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lEVPrfzNNzl-Z0_tSd5Sm9MsBCL9gsYPvd2ZsMod2hM","y":"X7bP7WwVbbCLq4GFySMGhEX-KnlkMY1Sxq6urnVBa7k"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -281,7 +335,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -317,12 +371,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault105514c1.vault.azure.net/keys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault105514c1.vault.azure.net/deletedkeys/key-name","deletedDate":1562686805,"scheduledPurgeDate":1570462805,"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/cf4a8cc71bc64528a08ef8e575c050fe","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"jdeRSacsi52xR6FvUirBwoW3ggJkbORU7uzCqnzmzyw","y":"7RT7S9dqt25zA5VCtZ4TZmbxTGFw5HJ3iOJZb1bX4mE"},"attributes":{"enabled":true,"exp":2524723200,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: !!python/unicode '{"recoveryId":"https://vault105514c1.vault.azure.net/deletedkeys/key-name","deletedDate":1562703785,"scheduledPurgeDate":1570479785,"key":{"kid":"https://vault105514c1.vault.azure.net/keys/key-name/4179a11e33db40cda026ac36e5830566","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lEVPrfzNNzl-Z0_tSd5Sm9MsBCL9gsYPvd2ZsMod2hM","y":"X7bP7WwVbbCLq4GFySMGhEX-KnlkMY1Sxq6urnVBa7k"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -332,7 +386,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:23:05 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_list_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_list_operations.yaml index 73e0d0d576ac..5c13a4da92e7 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_list_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_key_list_operations.yaml @@ -1,6 +1,59 @@ interactions: - request: - body: '{"kty": "EC"}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault110414cf.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:22:31 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: !!python/unicode '{"kty": "EC"}' headers: Accept: - application/json @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key0/efbcf0d254db454cb216c235eb41e230","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"DqE8x8054-PxrZscH7gCETUdMMhPF2Rul-IXjMWSqRY","y":"lOooXy7D_dajVzS2BeBbhYD90E-o8xVoW64W7L4RcHw"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key0/10626e164829440bba85550b588d606f","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"yim1XhkhZcRgfvVFvnh3_L13MfFY6JajfITmunU4q5A","y":"y2MsJCUZwp0tXYrHbSEdmd1IVZrdb9ceWHS9ddmmTUM"},"attributes":{"enabled":true,"created":1562703752,"updated":1562703752,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:32 GMT expires: - '-1' pragma: @@ -52,7 +105,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "EC"}' + body: !!python/unicode '{"kty": "EC"}' headers: Accept: - application/json @@ -65,12 +118,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key1/1ef229362bc740e79304b8fcb97af33c","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"79o1YF-5XF61jNG55wzkM_Ee-l3paajFtGFx7j-KpIY","y":"oM8IMpkNu2owVg1Aq5ES_EXEozNfpwkjVK4AB10EO2Y"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key1/000295519e314c34a85739db784acfd4","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"IZZmfX4ThSC9EwfouZVg0UtqQHNj-8gWXuSTIamHOG0","y":"mTcVI_hRYgu6sshha33EWJ8M2VGhezLAcIZMU9y8Q0g"},"attributes":{"enabled":true,"created":1562703752,"updated":1562703752,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:32 GMT expires: - '-1' pragma: @@ -104,7 +157,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "EC"}' + body: !!python/unicode '{"kty": "EC"}' headers: Accept: - application/json @@ -117,12 +170,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key2/95c1206021604e988e9dbc0ef3b34dc0","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"EfpkM0sbeFd3z2flY8lp6XDsdDUCCptBtiYncDGx0NU","y":"7KGCyeahEcV2ogGukm2KWD0aH7q1_fchkXAPf9df5QY"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key2/3ec39b7cb9494e4d8f891fef2f3e1703","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"Oo4kd4ob3pUIrF6UNKxKkrUmy0sCsHUUIRkoneenB5w","y":"yMnZxNH414w-Nr_DEDFhBKo4aYFWW5Qra3N_06U06zg"},"attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:32 GMT expires: - '-1' pragma: @@ -156,7 +209,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "EC"}' + body: !!python/unicode '{"kty": "EC"}' headers: Accept: - application/json @@ -169,12 +222,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key3/e430c498cb434cd8916c1e1d6f88e696","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"eUxy8zSjIUW7U7DDEzwEnUaJ_8sTqvHuglZmEdABn8Y","y":"ijnVsB83a5ZCHHxONq64IL2tf2efrXMp0pjvLA28mhw"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key3/64be18960e68484788a55fcb85e6e9b2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"zp_m1U6nrIVHUIhm8jTGrA_trj9X_cRHC5Emb8qDQLI","y":"kxnwMIl3z4M9hv1tvO7FPH2dLXGkZ5zDWKac1CvFrUU"},"attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:32 GMT expires: - '-1' pragma: @@ -208,7 +261,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -221,12 +274,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key0/6da9133ae53148ecbd8d82e920619a1b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"6l0EPQE6i0d7CdmnmaOn51otPJeRU0aN6hXArvwh1HDdwcxvb6vbu9MzfbUkjKzI5hbY8uX3p8aQqluK2KzFb3uZrdaH7SFsliNSpj7XuvTFB9Y1r7oFviGvYkWzKHFFmu_hnJ2B-h1hfjcdyQgYAUzffdee8QHMBLKZpSnoxlGwGi7cWdkZ0AU3_SDJnMr67sgDVWvQaSA6E3Mz0EAQ4YhJOptV3dPlPinJHAqWVW8GDmO2MnjHbwES_hq6hWgObPD3PScH6gisDfiSHlaY6d-6yzD9TfGr5GmhGY2Od2XZ0Joa6F8mtR_wXbwJATbyqOIyYV3RLLQerPHR607Q0Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key0/ca9f2b483f354b53a69235dbb9c6f8c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"3B5Mm_EKjUKTXqp7QbZwXygwcFg8fY7M1mBDPVaCaWv0GFwdfSx2POpVk1BqIOnB1s7GEFyzd59rTOfN9Uun6jhJdhDDSJ4YbSeptI8CfijJizFGlYGhRFUFAzubMTFJOMEzJierfp9Bdhr_H1-8bybvxIeAagjXNmIfXMto-waQhSHHO6xRC9BekYxEusL5JpZOhlfsltnhIzUGC68hXEWzFrgA4WEJOA27MBF3IQRm8avSs1-85O9bzGMEMQ4f_xsx0n05R94FrgJ8ld56ebUtCBBMs7409gTp5UUb0AkxOnddOjUYAD1c6Zy-mIMj359UnXgowIBqcC9GE1V4cw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:32 GMT expires: - '-1' pragma: @@ -260,7 +313,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -273,12 +326,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key1/95869f4f20bf4e3fb624a821879af013","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1cD8epcFPOuHSuP2139l-nLCdqUy_uV-1h3A74H6Z39sHj5GOcvm-OFcogHKK79fHoX8bhG5qkXEUm6C6sIQ7cK92KbukQLr6_oXjG-6Er67p8ft_UQ0b67XsyDwmzBbdTztX53FVR6nlXTyYIQ30esUq_PsHg9WoWWdBP8uvu1lWdoWKH1EyAnvrDAtK7OIHViPaePy0cdqm23WXuXo40-kTRXsjXcOxCictZ3LvpqjMoec72iuFc_4tSGZlCi5QvLcGgyUifdAJj7Eli9m8VtSTRMxBMUeLXrhmojJcHGJvH4UTeea2ejH8qZ4xZaWKyAmw7g61KMS6XWxdkGnbw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key1/c45b312d571f4b638db2e118ac6e01d5","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"whg-vzJxWQ8tNjBV2LvdQ7TSsQy8yOMnix1guZGJhyFOHV756hfD04j_32iyq6TIjJVCdUanwArn29UclH8olgNBerL6IenXDvg1sliAjRHXwaNi7JHmOjf3vUk3OaS8KJe-cHChCgLjqZs61UOGoPPMU7Sr4QtaRurdOqpvJNlTFQgBjiF6rN38GCfQHlb1LAPC1lQBvMmEDd603F18A24i048mnCcdQjuy4_zxLM5asr5uK9Bccht_SNHP2yrW_KDuyQZIoRxN7ekcD-NTzIprmYWE2WTl_hroBEYDTbB1ZC_VJLos1at38eybrBPX05Mq3lOpTd5vSWjrmhn0vQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: @@ -312,7 +365,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -325,12 +378,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key2/65a8a856a55c4b55b4a6880d5b06b7b9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"sxV8tskmsa0bDg53A7-ujA9fMxoomFPiMKyWZ4crthvLUEI_R18XU9v9tDz8ToRPeS0jbdoKErayQSwIIoJYVVOX8seiYPe-Wyyxj3rv7O1oKxwXVIcc-sSdnaM9jamTkm_uJ19CblRFVE7GCebv4XGXiBwjxXw68nU8Mlg6fXgI1IkdLNH-QNEtspdGbhZLp8_1KXjGFvp9sR4cACmx5VJX3LKX6VJmf10pjQhPFCFMFPIWnPwG34dngi1aaFljPZe4Pm3akFpUgQmLOFIK7Jwe3hCRJQkfVFl5AKRVHsl3BLN7WEd7dhvWmAzDr9VT_b7SeM1LOAepc6z1cl2Etw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key2/9a5ed78badcd48389fcc10cb0445a21e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"0FtmMqHMis19vsZSrhTT1HSMPB6qoL1fV6ZGG5IDRW-AZOVriBXwKPucBcxP7xq--OjP2RuL3pDROOgRWjVLlxy93CZ5IARduc1S1nQRcO2p8yHHYlH0b6DMebBLXW4w86BJsZXVcNkfWzs1q_FRgrO2e60vWlMUGamAyL8DD6ZsUYYfb-4ZVfc57XgaOky2CvMtai4PLbjRasLXvclypbpgTHeWzu4f4B4e0gVQdl_pK_O1ZlHey1T7r0sGyjPQn_x1RGGu1V4hmSdJQsyVx1D1PollYNYZWueHjdLEmdYYL4JXDlIy-8HY-VkGyM9pPt5lBz1sLd9XslC_kDNtmQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562703754,"updated":1562703754,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:05 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: @@ -364,7 +417,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "RSA"}' + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -377,12 +430,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault110414cf.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key3/3568c0f95eff43d285833097d5f2c518","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qhVYaSUgeFznRS-DojIpJB9wo7be0jZT1QXpo-MZObra9lJ0SaIBY3nYBOEzlJyYgHqQ17G6FhtK4vOuWNhm83TZlyBVL_lltscNKxQSUFzWREij9g4l-GAl_bNC5GfuhqW-ZLsVpVGV85sdtOZEW2XVuNFZj2YiqcM3pqzqF4wVJ7WpV1gb63dX8DkATrV3FsDXgZlZPP0txyiHbDBQ3KMrBiGR0ROAM0KSl0pB3zuiFnxJnc6jABeBMmdjvgIXyzxogSG3OMsCib9hqhTTRvhji4dLyvP4-ORAboarwu0Kqeci5Tp-w1_L7d_qfKyhA0MfiYLZ2JvrLB22v13KPw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault110414cf.vault.azure.net/keys/key3/66979fc0f7204ae2a24fe9368bcdfdae","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"jd1X1qjAP3Q7XHpanBwHKC5j_n-XzJNRaix2YNG9IjBrnLvkY9pkFWUMwEVABsEtJngjn8R5ulKm76r_5QPyX9sbvDmSkug2NgXpiWglzXE9NYnIULDN9wWsD99HJ3qefHDSElbCUGRpbzHouquGBnXbds_cigYT1lf3_Tu6UJDT_kFj6Ixn-oBj82XZDFfzmpdX0Oqtl0uCYSD86J0SY6xCWqdYKGTClBLQaKT7TIfy9R5Q286ZQ8qMJgNJNE5DSO4XilPlUV9mwrTwTIEawqSjCFvAmuq4pdY2CM2kvPS6bBxelJ5L-1t4mYEcCFH5Jwjr6jerCoB-Jqf7tqHA4Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703754,"updated":1562703754,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -391,7 +444,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:05 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: @@ -425,12 +478,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault110414cf.vault.azure.net/keys?api-version=7.0 response: body: - string: '{"value":[{"kid":"https://vault110414cf.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562686805,"updated":1562686805,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: !!python/unicode '{"value":[{"kid":"https://vault110414cf.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703753,"updated":1562703753,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703754,"updated":1562703754,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault110414cf.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703754,"updated":1562703754,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -439,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:05 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: @@ -473,12 +526,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault110414cf.vault.azure.net/keys/key-name/versions?api-version=7.0 response: body: - string: '{"value":[],"nextLink":null}' + string: !!python/unicode '{"value":[],"nextLink":null}' headers: cache-control: - no-cache @@ -487,7 +540,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:05 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: @@ -521,12 +574,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault110414cf.vault.azure.net/deletedkeys?api-version=7.0 response: body: - string: '{"value":[],"nextLink":null}' + string: !!python/unicode '{"value":[],"nextLink":null}' headers: cache-control: - no-cache @@ -535,7 +588,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:05 GMT + - Tue, 09 Jul 2019 20:22:33 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_backup_restore.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_backup_restore.yaml index ac4e54b5a908..f60652aec46a 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_backup_restore.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_backup_restore.yaml @@ -1,6 +1,59 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault103214bc.vault.azure.net/keys/keyrec/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:22:28 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault103214bc.vault.azure.net/keys/keyrec/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/f371da03e196435f88075eca2e30ddfe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"8dkTcJCcZ0JM383xGLQhD6n08fPHXoacd7nn4GJcYREbvstt7D-KRKLOSTscgPG7eFTwQ-FXW7bOF6h_7FPoSLU72QL2t87rLNb-JBU4N5Bx9_CAc88JLC3mIM_mrWjKUG0biC02w9GFptx16W3ptF3Qcq0hEjvZpyV_LhO-p5iPJffx8Eaxks-VImc_QyFIq_ygVYL6kdXENMNVAmiybe2RCai065dCrRLj9Hyy04NjwsTxBW4PIkr3HivcP9tqEpj2FLv3r80nF3eM3dU77X8KT1o3MSlRmzRPug3tETesp8gcouIQUOHD9KORMe6HN5uP3--UBXFw5gpJQxc2PQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/03ee7dca2ea544e59cb0b18c89526d0e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1cXx6yL2Hz8wk3-Xt4l79EF5c7AqhzgufzJf1H7D-E56c77-kms2KF49OfN5VYBLFS9v_8EiPjB_73mDimH6K9j_9JNXCjriAKFSJjLpgiFYe3McjtyezrMmJaXwlXkMOs36hCUEVrz30B0FXWb80zY38TCN1nTaPCcg6FqeebT3HOoElTFYHDQ28nYCeSfPuAofkXJv5T1SlnpKiP04sI632eV1_nx3JBxikTisOMgmQV418sC2GmWlnSxqB_pwfUxxHj_zrytQo56SOkWA0Cng77w6rjd5DDwBndky627nnf5qkyT9K38HLK-JSD9DMS-h_1RKAnSNDOy7c2f4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703749,"updated":1562703749,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:22:28 GMT expires: - '-1' pragma: @@ -63,12 +116,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault103214bc.vault.azure.net/keys/keyrec/backup?api-version=7.0 response: body: - string: '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmpNWWhFaWszRUVwN0pGS3IwZmxLV3dpbmhYSjVUZlZ4VGlfUm1kaFp5Mm9KdFo5Y2t0Vzl3X3l1U3BRR2VaZUowcDBTTW56ZmNiV2lnUVBaNGRqb0NLUWt2UlpNWmpobG4xYVRCZWpUQjZ0RVF4aGVoN1l4MGs1WlptQkVwTXVxMHp1eHYyTkpHaDNRei1ZVnJrRmFfX1ZtdjgtZEpWQmloS2ZTSXhMeW1zM1hINXM5U3h5MllvSDBxZVFzZXlqRGpTTl9aaG5rMjZVYncwMWtxSjVmZ1VmWXBtTEVpYnJmSmpBd0QzREhlNXN4UG9ZdV9ybHhNWU9zd3VCQ20yc1FJMTNmYVh2UDB6QzFYZnBfNm5wSGdaRVB6ZE14WGRLbjFxbXEwWmRCbHlxa29jUUZ2b0JPYi0wcm9yOFhoNkdPZlRLVzJpOFBUVG5QZFdyNUdWbUtSdy41UTFJMWdIa0V2Y2xCbGFCOFJTZkVRLnJXNzBJOHh3V2FkSkhVSEpkYjNILWhrZkNyS0RtTGlXaVJkX2Rldks1S2RnVEhSamRKVDZ5aGFHdDE1U2FRSlQ3UW9xRExyc0s5VmtVXzRfQkNVcmJVWjJPMjc0MUJzVmh0SzA3cFQyUnh0VGZUV0w2TnRRNmlxRDBqYWNpMkpqbGdnVkhpMW5oQVdCVXJsR3VDNFAxeHFibmhyMUxNenkyS0Q2TkRDTW1nWFpwX2o4TG5rNV94MkxrNElvejMzU3Z3V1laaUczaGdmd3d5LXpuTXJCUTQ2Zy1vX1pCd0FGLVBuUjN2TXNabjM0ZkkwcTRrcGNnOFRZa202VjRidllZZTRZM3ZyTkZCSXdiaWQwNFdyby15ZUQxNnNBMnhpdWhySUNVcWppNFNOaERwSjkzaVZpMnd5TDZ1T3otZXZoeUZLOFhfSWxtRjRtTHd1Q1doeUNobmMyX0ZZbUEwa0sxc1J2anpXSDlaNFQxUnMzajFKT1pfTFB0dGM5TlhpWHVnbnRVVkJVS2w4UXUybjVlNUtLSEc3TE8tU0NpMEFCTElyNWM2dzlZYnN4MlZhSUxIQ09FTFpCVnRDdWZtSjRrdTJXeG01T3pQenFvb241WHhFZzlzSkFOdUVoSHZxRnZNWkxHVHFIUjZfb2V6SnBZdWlMX3lOeURkVnF3NFpXT1dfM1BlcGdCLXFTVUNRd3pXbUJFYkh4Zkk3RlpWOW1WSndxb0xVQnBOb1ZDMlAxRWk1MHhXdzVteGpnWnFaQ3p5SlpGX0xxV2czM1BFbWVrRUFieG1pU3VvY3RLLW9OUVJJT3RWeElPZldKTHlFOW1GVzhTN0x2dnRNYmFFQktRUUJ2d3BBd2tqNUtUVTdZcHBaSG05UkFCVVR3UUlJNWtWV1pEcVFLWExQTGNVajI4T2RFd0RBVGFUM3VRdWttVjRSR094c29UZDRadUdGTmhMUUtmRWpMTnNtV2xxQ0ZLREh1b2gzLTBheW4yNENHZHJfSFNtOEIyX2xrWnlhdmNpZWJRX1hSaGowUnVWdGlMTmUyMzNTT1VJWTFjYkRRbHlmdXF6Q2RnRW9aNk1fVC1GQ3F5c3R4VWtONjItZzJ6amZCVG9rTUdrcU9kWjk1ZnpnTXBXbTFMT2tsVmJGcUwxQ0RpWFBMajdLSGppS0lyRnJiTm1UNlNUejFmS18wU0RURHdZM1ZJMnRKZmM0Nnh0aXZqRDEwUHl0WVFUYWxwcHBOTXhYUGZ2QjZ6QWJQSzRyR3ItQVZLRGhXVXlfbk9HYWdSZEZSOF9YYTVxWi1JQ3JrV2ZuMDNjME95VzNMNDFVQkprR2ZrT2Z4VlNIb3dmaXk3c1dxQkI5eVB5bGtSV2tyUmVrQ0xvYTRBQkM3N0pWRjl4S1FhYWVsZkQxZEU4dE9nSTFibmlJUjJncWlxS1I5X2hLUDVmckdMTXItNHZkNUNGWmhFNEFNSDh2YUdsWWg1TmNOcGd3dXNqMTltUkNxdkVVYl9kWFVQb0o1a1FuZ0VoUVdXaGlzTUx2MWtPVHhMVVp4bURBSnFVcldwS0VKRmlTd28xQUFqVEw4SjZ4UGZNQnFJSW5halhOWUo1THUwRHNZODM0X3p4OHpQVlpzdkJiQng2NHlGTTlmXzNNUXNFS0g1TVRzUFRHcW5BYkJqc2tZOUZaVTZlc3JrNVlHTk9iTFBvY2I3S1lhVnBvMFZVYjBlMVZzcXRUckJJdm5DZ0YyZF9jUmZacDQzTmdDUC0wbk5uQVNZMkhGSTBud2Y4ekEzdHZyUkJzUk9PSE9QbUN2M1FYc0htaWthT3Ztc3hsWDZQYktjUHM2WWtXRzc2SkF5Q2ZGREZfZ1pTa0RzWHV1TE5rYVNQeGprNEtZY1FrSVlTRjhpdDJSUG8wa0w1TVp6VVFsNEVsNk1aUGd4clhXV3NBM2pTQ2RfNmZDcGE3NjlJY0NPU1JVa2YtZlUzMEhJcVJxa2dvSFY2SUpDY3ctbWptcnJ1V04tcF9Bb1IwWjJrcHlZSEVta0tzWVZUVXE2Y3VyM2V5ajZMdDJXWFdWQ2dwdHg0OGR2Wk8xU2dwTWVsUTFsZHVvWEpVSF84d0M1NnFoQnQxOG1TdnJKSGkzQ05vaGJUZ0JHUkdTd2wzWDFOTk4tNFh4OUl3emZOak1PZFR6VGpMRVJLUWQyUm4yZHNZb0FoTTg0Mlc4WG9rSjUzbzkzb1pqell5bjJaOUwwNWlYS25XeEtTcmVvdnRCRkhWVVpNc3FnNEdBaTd0WHRzSDlLbl9FTy1Pd0t2eF93dERiU3BwQ3FUdHJGZXVjbmNFWEtPcGtKTjR3ZUxpY1dkOUF1SGoxb2pnMHNTa0xOTjZUSlk0SllXQ2d3amZKa1JMT2tnYmhMWlcxYzVMSVNROWN0Wkg3T01DR2JYWERLaFRiX01lYmNCWmFuTmxqb3VieWU5YmpmSEIzVDBvWXNJd0gtblZsS3luSG9ZTnZhc0t0bk02bko4OFVPdEc2c0QyYlJGejF4MnQtWVlrdWtnSXE1Uk5zQmx1cFdPU3lpNno5UWhOMHpZTmh6dmJuRGVEcm9yeXp1b09UbEZ6cmo0ZTNmdk5PeXhwTjV6b1Ribm1uamsyM3JuTnNpWUJvWTNTMGFqeEMtaFN3WG9HY3I2UUZ5Z0o4S0RkLUxVN3hSTXJSQzhkZlRyY0ZCT2JSUEM4NEo5b0dvSWU0ZmE0YnNCazA1Q0tfUll2Y2pFTGhydDZQQmVQVEUzYmdNVGJqQkZyZ2lscUVQWERHQjByRTJWMG8zbDgycl9ySkg5OTVtd1RMNC0wVTFfTnU0aG1DVnhLN2tDNWhCa09CZUN5LS1ISERmU1dKWk1SUVpaZjBIRmJRUkNlMW56bV95WWNBcDBlYjNwOU16WnBUdElpQzJZQTUzVnZsYmIxNVNRYkd3a2ppWGI2cmQwWHRaLUdxNU4zRE4xRl8wOTBLMzR1Uy1jdGNaOVZMS1F1UWxYbUxORE8tTVFMM3dTUmlUREZTTFhIc0FMTVRnU2JxUEZJUU5LcUJJcjVrVUtMTEg5YktROXdLM0ZlS1NHZGV5R0xCSGlEV01vUE9MSDNiMFdJdHdHMDYyRk1pY2hoYWd5UHltUzhBU2pmaEl5OUpsb2dyLTdqUXlLbE02d3U4ZE5wTGtGUFBTaTNOaGtVVUF0NGVZZFAyU3FMLWJ0N21TMnVVTXFTNFpKTC1PUkVZWTdwWUozZ3ZKMW5QbzJlSnFKcEZ4NDdTcG9YVjMxcUNkcWQzT0FYZWM2bWNWMktXZ1A3X2htalpuT0dxWHozbXdTUmU1NmN4UzhoWEQ3TVBXNF9QMWdULUswWVdPblVWcjRXR0tGNnFqQVNTQktJdFRyczZqS0lDS2RJM3dIekcxQ1ZIN3lQblhCSktFOXlQU1UyR2drY0gwaTlqQ1RMRkxUWWlRa3B2elo0QXdxYk9wanBSQW5WcmJrelIzQy1vY21JOHNZWjNBQUpoZWNtdUVpUV9aU1NrbmQ3cnU1cmRYYV9hNHRoMmp2eEpTc2tYWEprNFJ3cWFtSDR3NURWT2Z5amlydmhwLXhyekxwTHBUdWhGUmU0MEd5cHdIeGh2ZHJjUW5PLUdpN0dMbnRIX21FRDJ4ek5ncXdrQ0VMRXAyWXcydmNjR3hRLUpwRGJNWHdIdlpKbnhydFdDZm92WWlLNkZ6azFEZDB6RUlyakM5TE4yNVhJMGtOVS1sN2ZTSTd2TVlxZUlvSlo4eklqRDJwcHEyVmh6U3dnUGxHc2k1UTJvLUZndHhFUFF2LWJnYWRQYzVLdkR6SWMzRXJYU09xdjRyOXVKNGpqcU1va1JSUXdjWkJ5cFFkUGFVa3c5Nk5DN2VCSVhQRW1qZXpEcnBEZ1JneWxjTktkb25HZlFqWENFdVctS1U4ZnQ3UHpLUUF3QnRTYVM3R0wyTUk4U2tZalBHc19MUFBaQTM1cC12ODBzOVl4VXBOR0xhdW1KLUZtMGRVMXI2OUpXVWVBelVqdEdVd3N5cWVWUzBJbUJieHk3bFpWVGNOVTN5SXBSRFFHMklranpsVzVkcDFqU25yZGZUdlhrd2EzWG9mNWEwN2o2SGZwblZNWnZwMW0tMldCSlFPTmFkZ29ibmliLVVsV2dxUFY4dVBCSGVQbWUtYnNyUGppMms4ZktvSlJLRUZPdGpvWTdKMnhpZ2RValpoemtkZ21MQUtlMnVpWUJPUVpQbTlUZDJoRm16emEweklnOVRjdzNTRGNtZDlHQU1zdFZJN0dISnBYd0NqUzA4YXJfOVJBSVpiTWo4U2dxTEM1QXFqeG45VnRpOW5aQlV1RXF5ZFBmYTJzckRzaEozSF9oT0pncV9jaGw0V0tzcHpqZmxqNDd6OTZUaXZVZkc1WWsyMW85NTVUZnpfVkN2QmQ1Zy1DUlVvbjIzY0U4Mmc2cEFIN2pKSWh0YTNfMVJjNmI5S09XcnFlc0Y2a2U3N0Q3M1FreXBxUXFRV19RdkNtT0pVMEMzWmVWeVJ4LTVEa2ZNY1V5aEt6SC0wdHM5dHp2THM1RVdtdkh4MVFXRm5FdGo3X3MwN3dtWjZQOGtYZUVINDBVTU5MNjJmbEsyS2pvcEtweGoxUzFqZDhPbGtPVUtLaXctLXFqRkN2UTdlaTRfdS1KY0dOUXZIMm1OQm1QOFE0UG9hR3I2cTFCekVjV2xTbUlVSVl2Um0yckFoaUkzYmlDU0NZZ19Rb1BfOVRLRHRlZXk3V1hfcnhyN1NqeElhWEF3emtVMWItUFNBYlo1V2lNd3FhRE1XeklubUNaNTFwZ3JqZ1B3dXZ4MTdDeTdtTk1GZkxuTGppZUdoZ0FoUzhJRWlWM2JvUXJHRloyczlaN29FWHNwUzVPdFBGX05WbnFSRnVnT041aFl4UXFLdmUweEZ6YWFLVExvNU40M0pFM0RlQWtiMUJvVm1LLUtnQzhkNUZpMHB0YXp2R3E2MDk4OWxJb252UWpNcEpSSERzb0J6Z2tzSUttMzdwZldCYTAtZ3NwdEhGU2lvR3hPd256YjhrRVVseWpiYk02dUpDVk1vcDhxRjA1ZmdNSmVRLVVNZEtpeXNKbXllcW8ySDJ2cFdXUGpQS1FROVQwbVc3RjE3cVZfNk5uUXZXazl1Zmt0NmFFaE9MdXNvR2JjQ2lTRHdHMjNGT3RMYm52RU9Xd1R0bTBHMXIwYUlRcjhtM2xac1F3a19vdXJCTnkzVWNmWTJuZF81cGkybmc3WnJtM0NDeVdBdGtzb2dOUzQ1c1NFSDBJVUVIem5vWXMtUS1WM2ZneldFODY3R2h1OHRNQ2M5Tzd6bzhpNHZGaGtjV0ZBMXJFU1dXQzZwejk2QW1mR1QxY1hRWUNUZjFxcWJnM0VFa2t2RVlrdGdSZGE5eGN3MjJJN0ZnLWItOWNHS1NnZ2dYUWNfMXlHMkNrbFI3QkJKMGVRc3hZSDI0eVRSTmVQbVJNR09QZF8xRmRaQmszU1liUTN5eEo2amhzc2VyLXVnRGJUY0ctVzMyNHdocWN6RjFyY3JpejlSZnptTTZCZFZJOTZpM2Z3d2VUQ1J5RWl4YVNYNVI3bHh1b1lWZElnaFhZNzNUQWZzNFVfWlA0Nzhobk1qMjZGYVJPSFZyVjlsNmtDeThwTlJoeTVOUVphVm43TkVXNENGazI0a25ZLWo2ZWdXakdEcXowQUtDSFE1TG1xX0R6UzF0UUIxOTN3bnh1aXJNc1lqWHFBYm93eTVGUTcxMmlTSS1uRkdPTzhBVFhRODlyUDFvM1J5cnZtTFBoUEZoWEh2OXVpYnhkdDg0MFVITlJQUXNGX3c2WVYwNXNLdndpUjZoZWsybkFtbG5iTlEzdG1VR0pBUGxOQnN4WmlUeHFNUlRWWUUtendKQ19kR3N6eWU1RWFzcTVGRmVqakVLOW9XOWMzTlFlcVdWU3l2ejhza01ldEZxNEs5TzZFWjBGTWI5TEtmdXVndHVkbGUtdDVNQ1JPM0pGOThIZ25uVGFZVV8yTjlSQ1cwRUhkSklYQldLT1lvN0x2bEZmMlgtQllWZ1owU2Q4MkNiOHhsSjMtdmZzR2ktOXpNTi01R2I4c3NxdDgtR1BIZ0dkTEthdW5ObVVmcGJIRzg1MVNweWV3dTVxMzJOSUZBT013eUsyZEoteVpoa042aXU3QWQ3TWxDNlhEZXVRNDh1VXV2dTJCNXl3LVBDR25MVlptLWw2LUhJa002QWVOYzM0alAwOTRSazlFOUo1VVoxczktOEU4YWw3X3AzSEhfankyeDRnOUFUQmlUalp0TDJkaVBDYkk4dTV6OTZzMzl6R2ZtelpjYVB3MGVjdVprSWt6ajRqcVdCVlNkd1E4el8xVkdhbWNGUU5qTDN5OHpvTVlqWmNzVXRHdkRXMTc2RWlXSjJEb01TNFZNSFVGbktEaEFxUDZTcTJZczFLYlFRWkdsY0luWW1rV192b0hIV2lpVlBxZFVrdFlwbnFaQzVweHEtVXJ4eFJDRTdXMkhfelIwemlVOGwtekdjS3liUnI4MEs5TUFvWW8yVl95RW56TDhsdXJBRGRnNlZkUlE2YW01T3c0ZFNZZktLZmFPNUJPMEFTc3I3ZHhJNkVsUGFrdFNFYUNLVzROU1dSb3pudmtia2FmanlQS3oyQjg3bERkU3RJcUlFdkZKQWMxLWtNQTRFYXdLQ0I5OTJPRF9BT1F6UjA1bjAya0U3QU0wd1E4N1p5LVJMNjM1SkxZbmFNZnlFVkEyV2RkMmYtNHdyRm1sejNGbDZ4OXRKT2I5eTJVUEtuYVc5QjNpUW1pSEl2YVFkRG0za0JvQ3p0cmZOa2NrdTYxQ0JXQWl3M3F5aGVUcVNTVmR1ZHNyNVZuaDRYU1VJbExMWXRucmVMMnNyc2ZQOUItdzQxX2lranUzSko3NDJQZUtUb3hBR21iWGtjVG1jRWN4WDdQM1FTU20yWmtwd1I5WndLZ2pfVFFjLVdhRS1zM0pLbTI0bnVNVmp3YldYaFVGamEwTGhXUjRvTTF6WUxFRkVkc3dBLXNpYWtHOHN0YVNMR1ZMNlA4eENQRlJNTXBEcktDbXdiQVdQb0Q1YWRBTGYySjg3bGxmcE1NXzU3eFJGMUxyQ2hROU5KNnlGRDVfZFFndWpKV2JlVUxaY3NHODVLVER0bmM1T0JTd3NqQXhBaURfNnB0Rl91LXhQdjROLXVKQzhXaWFIZmM1RzBEckpoZzZLV1drdVpwVDdZLWs4RmZaZDVWdnNfQmF4RjlUODUzS2tfSHRUTk9iYm5sSFF0NnJUNEFvaVExLVhqcmFHOXFjdWlKTThlWDFmMTNxSi1jaVNxeEs0MXpIX0lxQTJialViWWw1bnpPM0lOSURhSzBZeXBRRlp0bnFUWU5zX0VvWE5BZWd5aHY5NzIyMmtYS3pMSUc5c2VjYVlSTE1nSlJXRGQ1QzRpdmdtVElORUdyZGV0bUROZzFaNU1NdUtnX2tQN2MyMU1MNTZCME9rUENXTFRLMnZwcURYby1VSS1IeU5PRnVIRFpVN0V0YVNjR1VzNjJNcnpvekpWOHJaQkQyaDMyU2xiZFRwaExQRXZaSmdUcFRYZ0JialZSaTRnOTF3bm8zbkpkVUFablhuUllVOWJuaGZQSURvNGFlcnNpQTVQZk1scUJsNFBEclppR0lqMUtrdWNTUzNlb2NYSUZBRXFlSF9yTjR3cXRpc2xpNXN2a2Q1c3kta1lfYktlYWl5bXI4dWJoZ2g2bi1zd0U5U2hXV3FfSlNqdmk2RFltNzBPWmxvN1REZ0dwUDByb2FWZzZrLW5HVlQ1ZGVRcXlMdnJ2ak40R2Q4dHp2eGtwVlplQWIxalNaYXJKblM3T2xsVkFjZ2J0TnhHSVNkaEpLUWM4WWhXbFE0N1JFekJMalBXV1dtUXVsNkxqVmdGTm5IOEJqNngtRW5jc2ZDYkMwQlhCaC1WR2xwS2lPUVRpRGhPeVF4LWEzdXBGc3dlVi1OQnd6cndDTVdGczVOT1ZwRWFpZzJCV1NjTENCRXNGVTR1cm9jenJ5TTl4LVRpRGVYbmVKX3BTd1hxaXNyZUxOYlJTa3pqZWtYVk14bjc1U04wOUJhRUNaSFhTaDJOc2dETy1jLWlSWmhJU0cwYk5NVlY1c3NZMDRSZERib0o4UDJmVzExTllRQ0h5ZDRFSFB0eHI0YlNxNlhUSFduVzJkeXFtY3dfMGxoQ0Z6bmdCbWlWRWxsbkhSVkpUX0hpQzhJYTNxUWc4VDhaWElwbTVIQ0dGLUhSN1d0SEJGWmZ5Q2VveV9nSWF1XzBjMjVlMlZ3YzNEdVpqQUdmdUZkX3FLYmRjZWVyUmVGeFZ3NUhlMjhPRGk0UkV6Q1hBbkZXRHNzb1VwTnF3cFgydExfMTZidEh3ZmFoeUxXVFN6V2RjbVNpaWdZT0I1bnk2VnpEUUJXLWJtWXFtOFJoeVBhM0h6VkIwdE1taTdQa2pZMS0waHBkckJ6WGNCa1pPZWRHTlZfR0FjXzJ3Mzg2WUt0WGRfa2hlcWQ1WHU5UUxNNDNkSGpUd0E2RTV6ZDc4NVJMTGRMZjhnVGFxM0xaNHlsX3ZXV3R2cGowVlJjenlwLVRXN2FXWHFwandPUm51QUtfY0dMMmotM1BSeTV3blJaNWZqTHpYSE1IZzhtU3duelhPX2RPd0JOcXotLTVZQUhLZ2wzMW1wWFNQMGQ4NjBIaW9EWFVLUHdPLWJPZWZ2WG9ncGhkTHNHLVF6aW53MFZLWlhqQWdzY0t3ZmpDVDJRMTBIWk9sYVZNbUdRU0VPaG9TZnBZQTZhMnpLVFJ2QnZmdE1pZlJlRG5YVWQ3S1h0Z1ptSEZuYWJwNll4MDVYMEs2dnd3YTktWlVGZDBJVGRtNGRjVDdiZ2RkTzE3TklwbEVua1JpN3Roc01Wd29ERE1hbG5IY0NsWW5DSlgwZXFzZncyTEtqZ2VxY2dPMjJFc1hNYmVsNHNYSVhjejY3dE9TcDlUbXU3dDZHbk85bEVwWGo2dEs5V2RGZXIzWUdWZTRwOXFLNHFmN01ST2NmcGZqSjJwQmVFOUxXMXZERF9RTWptRlB4Vi1PWXNSekMxWmhGTXNYSmZDRExCUXhKM21uYlFZSFNFcnAyalk2MkpPbzRlVkVQNFdybk5zS1pxdUNuRFNISG5lUnhnRE9PNkJKV3BMUWxsTXJNS2tIVHp6Z2w1N3EwMjFBSlF2cWU2Mkxza1VoOGtkWldmbktoeE52NjBLN1N0Zjg4TVR3VFF0dEZiS1hLRy0zN2FCVEZENHVPN2F2bElIcVpaU0xfUk12OFZrVlpRNkppXzhxTC1FZGFVS1FuamVJRFNfeTd3MmFmYXJLT3J4UGRaWm91eUxJTXFFWE54MGF3T0xRdzFCdF8tNzV0QkN5bm5IWW1yeE96Um1Ub1BDdEpoR3RtcmNNMFFlMlNZcGZfRFVYZWhNdzExbVU4cnhvSVVSYjRlMmI0QjFnVWJHRzV3dUxYRVFEUHYtNEZsYl9VM1RmSjBVREJLcFZ3enRyam93cG4wVUdjaGNLMzhuTjA3M1B1Zi15NW9Vb0FNakRMMlBoVkN2NG96QktuOVZfYmRyaVBxY3hrQlB2YklsQ1dkSmlDdENHekFKaFhIekJ2Xzh0YlJGRjFKS1ZfVHhTZW1ySzBzQVRCVG90elhEQllRUkRYNl9vNzdLYUJlMTBpUjNKbDh3WVBueEktMHZhMWliZG1ScFdSOUhXR1RNdmxfVkZkZ2pkNk42R2k2RlRrNm10cWR6RFJ3aDFPRGJ6aGVkdU11TlVYa01YcE9ua1QwR0g4V2ZMc1VzbUppY0VKS0NUUWRwR1FRLkh1eFU4NVVxc2dIYUdsaHRrcXJMX1E"}' + string: !!python/unicode '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLlczVTRGdW94ZzNjcWt1dDdtS1BIV2E3aFZMNjlPcXVhUElMSlFLcTZ4SXlSeGRTQ2R3bTJlSVBXVURDX3QxajlwbWdFQ1NudWFPdVQzcDdUZ0NKRUVEZWF5di14bW45akNyTHhIaXFweXJVTXB6Vm9uZTFQVjJBdWZiZUZORTZ4Y1NtT3l3Q2c1OVAwR3JJeENVZGEtYXBNYlJ2YmYycEJxaUstYUd3bDZWQ0kwRGpXMDZWc0ZUMFV2TWRDbXFHTk13ZnVQRUx6WGFnWUd6cEdxaVhhZFJkSUMzVlg5MURPeXNybEtjU2t3WGZIaklEVVBzQ2FYejZfV05sMW5fSUJzOUdfeXZnZ2x3MlhrRDBsclh1cHdfa09LdmlUOVl2VGF5d085SzdNYmFZU3BncDRVN2tZR2tHcS04ZlZzcUd5Z254M1lQdHlaUWxBSVZIZ0JJQnFMQS4weFlpblFiMmJyYVdPa19LSHZMb21RLm5IZXJPc0FCcTV1bl9YVVZwR3dWeGIzTGVYSjB0Q0R4WWZybWRSZ0hoY0NtZGZLOFdHeTBKbnZZa3JoTDdtVnJrZ08zZjJVRWczUmd0TklWZmdEcTVtLS0xeXNubW5ncFF1X3JEcmZlWld3cENFNkJjM09RbG9sbjhzUjFvTXY1SENyOW5uMVNxdThfUnFSZjhXRHpYLXBLRzJ5ZjRva2pTbTRlMmpfb3Nvam9jMEQ0SlVDYXVGSGtaaW1Qb3M5dFdvdTc3SmtyazJMX1hTbER5UHhRTnR2NkVxTUFaUnZOT3Y1T3NlR2ZTRUVKSFozd3VqUVRaekRxbldvSmVCeU5UblFPSmh6NUE4RG5wbENJbEdabHpYbGRxeUY3dGRNdnNjZmFQdU9wekh4bU5Ja0lucThJNE1IaExyWjFRU2dBODhjUm92aGE1UklsLVRhcGNCNV80OUo0UndENFRSQVM1d3pMTjR5TXV4YUFSX0NBX19DTjJjREtHcVZPdUM2bXQ1YkZqMmM1SEszUWNBVUYydU9qS1h0am5wcnM2WDhjTk9UaW1TTkVBLV8xWGhDWWJIbXBieC1nMDdrb3hlUFI2dUh2bmh4bmZZVlU1ZS1mR2hzRkxTS1IwNnFyVUw1RVhDZDQ4S2JqaFQ4RWRwaU5yMWU4bE1UNjB4NDlFcEdFN2t5SmRxZ2dVVWV1UWFxa1ZQOW5DRFdoT05uZUc4QVdJY2ZOcEViT0E5QVZ1eHk3RXFFWGxDeGw1LWJ0anZZUXJGbW5WWGgxS2FDdnYtSVNFcmxiNnlacVlSQ015TzJORE40REFrM2hXQ0xUT1JFN19xdWdmMFN5aGtTd0hRLUk5MXpUSThOTzhYQXFLYXBxSC1FOTRsNjk5Ym5pRko0M3A0QjdJT0NRVGVXTW5tSm0yWjBlTVkta1hXdG50R3d6Q1FfXzlVZU4zVjlsUmYyRUZPbnJQRXlTU2s3bktaM0M1SDFFYnladlJGRlh5R2JsS2dYN0JUM2RaU2ZXUklRcWk2b2dGVWZUbFd6Nlk2S2p6a25BOXk3RmY0bkpWZlJwMHpaWWxTVVdZeU5Nek5LdUxxY3oxVlJDRnpJbFhGRmN0UVhWQ3RVdzUtXzlUSTB0Z0dCMWtmc0pHUldfckNVdFJyNHBDU1QtS0pTYjFteV9za05oQ19lOFlELVBLNnhRTGtzR09RSEdfc05aX0VXSEh1bWJ5ZlBoTGIwNXJJTURxVEtZa2NfY1dVMV9tcEpSOFhNbll6VGZtemNzMHAtYU1sV1ZBWjd0cDk4bWJ0VWNkcmVFbk1TZy1lcWFXaU9jRTdrUXQ3cUx3SmxWRGhLcjBaeE55dExHVVpIWGlGOEZ4bkhuZDJ1OGRDR1FON1ZBT3l1MlVCeS1zckgzVjVfeWFMcUdBak9rWkFFSTVhWGo1T2ZNRkM0Y2RZUnB4SXJxRloteVR0bWphRmRwZU5jaHhqRFNqVTBvbVZ4LTF3c1VxMHpWNVJFQWw1Q0lpSmxTSWdlU0xhVUd3dndIUy1INXpMdkhDMG5jRmd0RDlad21ZQ05hMGUweG05MEpnTXllUkJOWmhTNl9FdnIzZkpIU29Uamdmazd0M1BXaTNPcDB6MFgwWVRjQ3BnSlliZUQzZExnUmVpM0NjM2VGLVZVeDkyRW0yYnhOVHUxR0FldzRUZ282UExzdHVvSDZETnRyTjkwQjV1cTQwQXhLdnlwdHJDM0RSMEN1SVZDTnRxM3dnOVJ5SzdCUDBWMk1wQk9uYmNMYlVQOENxMWMyXzRORWdLMFowZVdYOE9vNkR2Nk8xQTJ2UVRaQi1SSDFKVmlmLTliM25RTWNrbUE0eDl0TmFCR1ExN2hMTTdFUkNramJJTHgzcE9kSFJQbnFnWG1hX2xHaF9rclM2dzFQejJoeUNtd29uR01RUEVLb2ZyVnZfeUxTdGlWd1hYZWZkT1kyeTVkLXpjS3p0OWlmcHRJb0ZzalM4VEJEQnpWLVEtZy1OQzE0SnFnVWhaNnpZbGtqcDFaeFZXMkpZQ2lMTTN5bTd3OWdiYzJ0OWt3ejZLUFlESGtzN19MOGZtQmZldGwxcHJ1SjdxMVpWZVNKbHJtaGltdGNSay1LU1dxWmJkN1AtYTRKZUNkOERHXzdmWWVJUG5XX2hYeXRxYzBtSFJlUVJSY214UGpLTU5OaENKb21TZ1ZLdi1LcVViNXcyQ0gxNDlRdWFsLWRPa0poYW9INkFMdTJIVUpaRENyTEpyTjdKZlRlWVVyV2h3MDMzRXIyQ2xRRzh3a2paY0xwS0IyaGhRV2RwZzA3U1EwXzVGSElmZjI3WEFGZ3pmbEhBSTBaOE5hTUx5RGRaN1JuWlZWczdmUHFmR3hUc2czcUlkV3ljTVdZcHhTdmlubGlYSExjMzRjWHdZTkFGeDlQaXplZ3dJWWVFc2w0MW00bWJueXl1Nmw0YXJfVWx4TE91MkUtWTg5cGwwb3lCT1EtUThsa2VYVy1tY3NKcGViVW81OGN4SzhYZ2xXOXA4UkE4Yk5PZHFwMmJMZ0N1SGZCQWxXc21NS2JRYTdHV0hvOVQxaVRENWV3YmRCaHpIN2x3WHJRVTN6YVZQRUpqZWw3QVVNWnJod2JLM3VXR0ZVeDdpTHU3Z1BoV0dLMlAySi11cWFzQ3ZLdXhqSElndE5hckFzOVlVYnZsWUloeGtOWGZZS0dCVVNHZVFoR1B5LXJpeVFDcll2ajBmLTVOejJtRzllazVDQTBIb3JFcllsWEh5UTRZZDBRZzdCN1pOR3dmX2tHNVZKOXczVEhzd1NNbjNTNTV6Rl9xaVpBS3IwbVRHbC1LOWxIU21GejNPMk02RFVzeVpRM3lXaUlqVDE4RF9ieW1idFAzdFo1WHczOHJKdVhidGJNRGFONGdwalJwZ3lyU2gtbWtfaDNlTmZ5RWZVdWN6UXdZZzJIUy1IN2N2WGlkUjBRaTJobmpfeWhRNDM5cWpSNjQ3Zk81T0lfbmVGdzU3bkdXLUJhV1RTbmdxMDc1MkxkRi1tVU5HZktKX3dUclRoWlZ2aHI2WnFXQzRIR2Z2dDU1WVVydVdTU3dwRXVsWDlhdGNiUzB4cXNsSm9oRUZEOEdhaWVQeF9kV2Z3Q3ZNQ1NWZC1nVm5oYTNSRG1yeTJGSUk1MGYwcGVGZmh1ZTFDdjdPaWxHTnl5cXk2S0N4TkJCUzFtRXR5Zm5IcDhJZ3J0X2JUM1lTVU90aldGTDc4a0g0OUlEY0puV2t3MUJQSWVJN1NTTk1qRXJBeW5RaDhwUy1XUl84azFDZ0lBUnc3dktMcmdOQ3ktNFEyUTBpQ0ttSWl6R3UtYUZsOVFvMHV1YU13WlRrazZCR09yQXFacVNaWklMazlPTFFWaVI2QVVNUldqakRlYVM0RS1wQkFvRWludWpiV2VCU0pTZ0NWcUp0U3RHN2t0enlGc1VPNnk4VVhJRUpNdzVsc0JwNjBlNTJSYmRaS3E1YnBSTzNDN19hOTY2NkRvcnZMbWlSVFF1U2hGellsSFZpSTdFZWxvNW05RXQwR0ozOUlhbVJpT2M0YnRzWllnMmJjRmFoU1NoNWxFQ3p6XzFnSElSakh2SVMwUkR5azZ4TDh3OWxOQTg3TXhSamVoajNkaERjUzlIcjJ0dEVDYlI2ZVZRMVBJY3Q4R1RqYlhzek50OUdGVkY2cVlEdHNFeWRRTXYzQVFYV1VOcjJXbFFtb29nNC1Ua05mSEs4Zl9ONFhUa1RHWUh0WWV1Um1CZThqc2gxa2hpZG5NYjdWRktpUGVXRWI5cVduZmVETWpzTXZLMmV6Vi1manNrRnFNNWMyVHM3ZVVHeHpRcUVlLTJ2dTlQNmxsM190MHVyb1NBTlNTeXlLZzRfbmxFOERLYTdRVWtDQ29mQjNCTlVQZGlCcEFBamFjVEtweWlnSURRcEVlUWhyV0JTQkItWmJpZkJFNVlDMzc3bWdzX3I3TUF4RmxDNXYzVTRpSjJaTVk5UEJLUWlrWGhfMlo0LWRFU2NIMmNZeXVwcG13Vk5Rd0kwcFA4T2dfRUxxY3hJNS1JZ05wMlB1NnpJOE1Rd0taSEZZbUd4b09Gc2tfZXlxVU5nRGZoaURUN3Rqb2tSa3lYYTNIOEZTUExsRHR6V2w1RTA2QVNKY3Brdjd0TEh2NG51TS1fVlBQNzFXT3JqTVl0b285Ym9RM250eWVGRG1wTXI0bDZrWE1GcmYxOWZBZUtHaktSeEV2akhnNlU5cUN1a3VwZ3puVFdJZUM5ak8wV0dSQXZ0QTZ0REhjMExEWEkwanJLSFNzMDFfSHpOMF8xbEJ5SXRvN2lEWkhYYmM1YllrcF95QzVpa1p5a3JwelBjRTBBT25MSEZuSWs0aWhzcllQSW5jdzlYOGc3WDRPVWh5YUhiUXRzcG95cXVYdF95N193aXBzSEE3a1diUDZZOTJhUHdZTFQyR2RvUkVOUWhBTDNKN3hER0V6aXJhSDVOT3NDV1BmVGYxV2liYkdBVnZTMVRndFlYOGgtUzdZaWx5V0lEandWY2dSSEpyMjdXQXFVWlZFamVaWDB1cjZpU2FOdXo0YjNVWVh1R2tGTFlfY2Y3Qk1XZVFXUkd5Y2ZSVlpLRGxxb19fRmhvbU5hUXZibzRFQ3VSMWNqMXAxS0h2VVR2bEpUd0EzM1JqengxMFkzemY5YnBub0p1OWpHOThQTW1tY1djVmZneUVfLVBya3AwaXJTcGIzVlNYQ24wbHBQbjBWRHlySTBERjZieW55b2xjVk10c1JhZUNoLVBYUnlLeG0tckZCcnVXSGxNcVI3bGRfdmowNk45MVp5bkppdlRWN2NSR21GVHFjeWRqdGw3MVVhTDhUYXc0ZU5YWUZMV0xGU1R1TXFocEVSb09haW80WVI0UzZPU0dzRThTc3BQT2NUVDRkYnVpNGNIZGNLeW9Yb0VFRTJTMTc3Um5jajJMTlRrNWc1RDF5SE9mY2VFTDhfbDZ3dUZTWmxKQ1oxZGFlSFluSEFqcDJ6MHExVmV0eVlfUmxmd2Q1aklsVDliSnlzdDJyTEJkb2xmc1pucDU0MkpGVjU3SDNGb3o4N2dzbXV6Zmxoa0Y5Vk5wamxEQjNKMkMteTFvaURDNlI0NU1kN1BnMnR3RmpTd2sxMlBqUU1Xbk9WUFc0aTlGd3FtTjZhQWN5bGZKNE5VNk9ZM0hVYmFRWFd4Q25JWFQtUlNaRGdLeUZGWlVGYk9kVmZxSDlwX1drTnFkTU5PbW00UlZlbHpvVnhfYmNSRzZxWHZCUzZKUFdqNDhWY2J3SW1xXzd5cjlHWHo3RzhqQXFhWGpGLVFRTkxLXzQtZmJ2dDBTSTg0RGJzS0JZNXFuQ3BRUXpoVE5FU3Rxdmw5SzUwRTduYmpTNW5IOXZ6UmQzaUp5UUFvX2g3SVoyNDF3TmlPQjhRdkFvSi02X21pQUV3bHdTdk9IOXZ2LVplSEJpWG9Qd3ROdEd4NDdHU1FDblZXNjJhMm5rbUlXLVlyR3h4bFNlYldkOTRkaWtCaUxZOUxRQllZYmdDOHRfcWZObG80dEVxRFZ2WjVsZUVwVnR3b01IYlYyR1dEeVA2bzI4NWUzcm5ubzRFZjlDc2lrNC1CSUM3TTdtbENEa2I3bUpWT2x0UC11emcwME5ocVdnd0NZWXRZYmZ6ajFZUmNfOTNCX3pFbXdELWRuUHBEYVNRdE1KVEhDVWNNY0RlMUhaRGs5TGJMSWJ4aWpVbXZGQl9nRHdJdGZ1VjdtdHRJS1RDMlBzUXMzQlowZFRYb3lvcktUYThWdi13VktpZ1pxbENmR2R0S19FYTdlVnZKa3FyUV82MTBXZS1uRzhYUmRaV2VaeGJUZkNyZ3UwYkhreXcxUmFQdndVMVFyQ0s2ZlpRcktoX3JZcjhWM3o4dnBoYXBOMksybzI5ZnJTZlo1Sl9BbnZyUUhfLWJPT042d1M2X0ZkV2llZUk3U1FUbDNHZVVjWmp6T2Q4SEpPY0s2SWVrTTdUTEpENC1JU1FHMFgzMjdoNFhnbTlwcGRhMUtXTlFLVzhQYUhIZTdwcDdZb0k1SXltajJWVXREUGxTaFBDZHFDa2NsZTJ5RERRaHhHRURoQVU2RlVtSWxOck8zaWdUTUx4Vm1QQko2U09vNXU2ZWVjbnJzN3lrZlN2b0NDZTN1bi15RnNLNlBWdEVoSFpHckc4MHJ5cHdOa2ZPTmkwNkNIdlBDeGF3RVdXUWhfUWx5V2JFTzNVaEpOa3luaWlZMFRBQzBBdDhZWGo3QnZQeHRvb19Edy02aWplVmdid3ZhTVVYdk14YWhDUHFlRlQxLUN6S3lWTzNHeHVmVWRzWEwzd1dYWXo2RDA2WmFERXZpSG1TV0h4QmVweUpYcEhkY1JsVU04Snd5U0VRVjkxVHJkQVJTMHJNZGlvRzJrVzNjVkYxbzBkX1lQbkZidjFJal9MQ3BodF9wNnZXeDVtbzNJRHJxVENTMDM5dE52YVk0V2tSY01yaXgxWDdHTWdXeFpZNXFOUFZrUWlvek8tNGE0UWlmS3JjUDh0VTBQYkxaNzhxS3Q5dkp2elV2Q3V6YzJuMEpneXJJMkIyN1lTX2E3ZldkblJ2T1hBT3BPdTR0Ny12UUtQdEJpcWctSWhJeURYLXIzVkg0TE03NUNfVzJHNVl0NUhiQ2FKb2JyOGdldENpOHVraDZ4LWVobnhHOTQ3Y1dwaGFqcTRfaGdvVG1Mb1VBdW5UNmVaNVBfVmc5cTg4dF9xbV9kZ1cyUkxkVW8wU09qcDFXWDBWODNIaTVJSUlTRTljMHRDQU13RDZSNXRNUVNlLWJEdmdqbTNwXzJ1enNyTE96WVctRlhaSlN5SS1fMkUwbk5UT2M0OE04cWswTVZoMThKaloyWUVpcmE3RVdUNlRlZzZ3VlpSX0UycEN0ZzhmNnpnUi14aEtaM2F5WU1MaUFZb1RJdFdhYk9qWlhMd1pxSjk5Zi1qanFUTktPS2NjUlhMOXIwMDJ5NlhUcnBXZTJ3aFZLTmhNZ2lxQzZCZDJGbUhzWjR6T292WXhsQlRtY3l2STJOWC1EMlBqMktnNjg3MmViQmpGREFLYjNMUFk4bjROd0VKbTdNMlh0MzBGYlF0cmFLdzRSNXlUdDByeVM5SGFnV0E1ZDRoSHVqLUZ5WG1rSmVPWkVtZTkyUzB5NWl1c00wMmlXQTZaNjJ4WnIzd3pqV3Y5S3BDOWxZZUdwU0xLSU5WTE05eEhQQmlxNXVrYWV3RXcwVGZPZTdLdHZpTHpRVjhSVnF4UVpub05sUEFRSGcyN2JPYmxsTld3WUZTNHprNU5pcGcySExacTF2UndxZEh5WHJNT2JxenZCWGY4MEJnWDNGd1ZWRVVEdi1VVndSQlhhYS1xOV9rckRxM2lVbkhNRHFSSkMxdk9HVDB6eWpTNnY1cDNwemtUdElQb1ZDa1BETGI3SlJMUTMyU2pJNU1Za3FyR0tDTGY5M3EzSXFJZXRjaU9Fb2g0TUhsTmpLU25nRjJ6dUwyNFEyWnhNdjJJczRYWFBTTWh0dEJnWlNRVXFFaXp6bmM2OWd5UmpLMVdFLWxEdUdiS0NOSm9WSHRGV3V4ZW41eEs0TWlVUEJhMGVKbkMtcklnbFFkMW00Q2pZbVd2RnkyNnowc251MWM5M2RCblJEekFhVnFNcTBRZ0ZpbnV6d0FMWWJNMG9SOFllal9kT0JjY2RUeVloVlVmVGpFQ1JOXzhiazFkTllZMVNMUjl0bUZlRnE2ZU1iNEw4bWE5ZHFCRjNmNVhSN0k1OHJnODlGTXBmZGVLMTRza2xnZ2M0TWRReV9EOTM3UHZabUZrTXZKN1JBU0Z3RExWbEx5V2JGbGdUX2FHWTVtTVdZd0dPQVhZbnduTU5tSi16R2VuMGVOQWlhRFFRUTl0eTQ3TUVKdjFpMWxvVmI0RzM4YVl1a2VhdDZuR1M3UGppdUNGcUNSVTFGLThOd1gyXzNPcDh3MjdUSkZ6TjNtQktGQnpxQUZkYWRPVFJpbFRqRnV4UGk0emZNakhxZUhFa1g4TGIwN28wY04tVUN5QWxBMnk1eEJoczFlM1lSZUpvMmp5Q01sSF90ZjRjWnZIdEQ1MlRXYWt5QW5wOVA1MHJ2c2JpTXg3R3RkSTVKYlQ1LU9EUzNORUQzWUdHR2JTcTFSaFl2TzM3ZzMyWjUyX3hYOFpnaDQ1eTdNNVNDQ1ZyeUpjb1V4ZHNidU1NWFEzbi1kaC1tamw4M1FuTTRtTXNpSHRCdF9TQW1BRmNCQmh1Zk5sakxjbzBnX3ZrOG1IZkROVzgtSmpjblVqMk5yRVlBc0ZUenpnNlNJX1dPYjN4eUhFdWdfN0J0ZlZCb3VWWV9EN01ra05SN25XR2dXdmozOXdyYkJxS0prbVNacEF1RTBFMXY4SGZVMElOVFpFSnFIQmFZUGhWZjVqdDI0MUZpRFUyVkhUMkp3QTFiZ3pyQ29PMGtjMGJCQWx6b1I2M2lWbWdUS1BjTkdrMzJYZ3R3RmNIb0p2QlB3cVg4OEcxbkF6RlJrbFFQcWtnY1V2UjNSbThoM3pkSGhmaEFoTGxaT24tYWdkNTl0ZEFVM0ZKSmFIOWVZbnVKUUd5aVcxS0RwM1RQcUt5N05XQ2hSdGRPOTByWnhVODEyX09fNlZQRDF4YkVQXzVpR3BMRFZneEotNEIzYkJnUGVUSzFrZDBNRW1rMEhuVGtRemVFVm9aNEtPZ0ppemE2QXhyeTduZWNzSEtORzlOeUlYRWU0SzV1cDQyalBKNW1DU2lBLXZ4UVNqd25UWWhyNkpKTXYxM0tNN0hrX3c1UVJ5LW5waWlsXzdTU2d5cWptMUdqTzg2SHJzVm94RzJaN3huR2hlMEh1NExfMFdsSXppVzdaUTVTNEgtTFVZV1A3OWJxNnNsN1RWZWxYaHpCVEtvLWxvem9MaVNoUlU4MWJtR1NSYWxEZ3BmcnNWR1ZIUjAzcGRhY0dZUU42ZWowR2dmMi0tanJtVFdEZ3ZGMHdGeXdKUnJLdzRwdWcxcldNM1VwRHItd0tvbXh1aVdpRXVhcU5MNTVpSVg0RkZScmhCMExXSTZ0LURNMU81NmZ2czhMSFpLUHZCeU9IdG5XdlF0WFhCb2tDRFFRSlpJcjNRM19RN01CdmNZTlNELUpoLTY0a0E2eGFQSTN6MzFuU2J3OGJFWFZ4RVBkVGZhV1RRM2RoemRVbEpCVEV4bzczNzctYWlFTDZBVWlBWmNpRWFhQzJjS1hXY3R4VFY0czlEdXhVQWlvR05YRmE3cy1Pa2xNSGdBSjU5MlA1Qmpfc2ZXNkdmemdLVmhuQXVBODRqTHRUYmFselk5QWc4cmFWRUJxYnhrZVloLTV5MHZsQk1sRzZCa2w4aWFSWEs3LTJLQ2VENFJRVFBCMGxwRzV2YVNOZW00MVEwcnZWNnAxUXVBWnA1YlNhVW9xS3B6aWVCeG94Ym5pUFItRzRGMUR6YXhSS1A2MVFHYXpUR09rRkNMNFhOOTVoVUVmRHIycXVtVUhiZk51NWRTT3daWkRqbTVfTzliMVRyWEcyd3M0YUNUSVk3UUMzS1FDdlJnZWJzTFUtOWFzQlBNTm11Ukh5aUJ6a2pBYkdkU2Fkd1BXbnM2VVphZlE3djF1YS1hOG9ZNzdEa0FFU0FhVTZIakE5YldfczlSam5EZ2RNTC1OSjdlMFU0T0dIT1o0WGlwWUR0UE1RNXVsX2xHZGU1Q2pzQ2pBdFRvTThNTDY0WnQ5cWEtM2ZDTTg3a3dyaHJGcGx3QkdHWGxJeGhFaDE3bDBKU0ZOYkREZEY0d0hIbkdyR3lsd2RLT3BpMDd2eS1RLnZra0FYU3FYZHNFYmRiTDVRNGxCcWc"}' headers: cache-control: - no-cache @@ -77,7 +130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:22:28 GMT expires: - '-1' pragma: @@ -113,12 +166,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault103214bc.vault.azure.net/keys/keyrec?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/f371da03e196435f88075eca2e30ddfe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"8dkTcJCcZ0JM383xGLQhD6n08fPHXoacd7nn4GJcYREbvstt7D-KRKLOSTscgPG7eFTwQ-FXW7bOF6h_7FPoSLU72QL2t87rLNb-JBU4N5Bx9_CAc88JLC3mIM_mrWjKUG0biC02w9GFptx16W3ptF3Qcq0hEjvZpyV_LhO-p5iPJffx8Eaxks-VImc_QyFIq_ygVYL6kdXENMNVAmiybe2RCai065dCrRLj9Hyy04NjwsTxBW4PIkr3HivcP9tqEpj2FLv3r80nF3eM3dU77X8KT1o3MSlRmzRPug3tETesp8gcouIQUOHD9KORMe6HN5uP3--UBXFw5gpJQxc2PQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/03ee7dca2ea544e59cb0b18c89526d0e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1cXx6yL2Hz8wk3-Xt4l79EF5c7AqhzgufzJf1H7D-E56c77-kms2KF49OfN5VYBLFS9v_8EiPjB_73mDimH6K9j_9JNXCjriAKFSJjLpgiFYe3McjtyezrMmJaXwlXkMOs36hCUEVrz30B0FXWb80zY38TCN1nTaPCcg6FqeebT3HOoElTFYHDQ28nYCeSfPuAofkXJv5T1SlnpKiP04sI632eV1_nx3JBxikTisOMgmQV418sC2GmWlnSxqB_pwfUxxHj_zrytQo56SOkWA0Cng77w6rjd5DDwBndky627nnf5qkyT9K38HLK-JSD9DMS-h_1RKAnSNDOy7c2f4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703749,"updated":1562703749,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -127,7 +180,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:22:29 GMT expires: - '-1' pragma: @@ -152,7 +205,7 @@ interactions: code: 200 message: OK - request: - body: '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLmpNWWhFaWszRUVwN0pGS3IwZmxLV3dpbmhYSjVUZlZ4VGlfUm1kaFp5Mm9KdFo5Y2t0Vzl3X3l1U3BRR2VaZUowcDBTTW56ZmNiV2lnUVBaNGRqb0NLUWt2UlpNWmpobG4xYVRCZWpUQjZ0RVF4aGVoN1l4MGs1WlptQkVwTXVxMHp1eHYyTkpHaDNRei1ZVnJrRmFfX1ZtdjgtZEpWQmloS2ZTSXhMeW1zM1hINXM5U3h5MllvSDBxZVFzZXlqRGpTTl9aaG5rMjZVYncwMWtxSjVmZ1VmWXBtTEVpYnJmSmpBd0QzREhlNXN4UG9ZdV9ybHhNWU9zd3VCQ20yc1FJMTNmYVh2UDB6QzFYZnBfNm5wSGdaRVB6ZE14WGRLbjFxbXEwWmRCbHlxa29jUUZ2b0JPYi0wcm9yOFhoNkdPZlRLVzJpOFBUVG5QZFdyNUdWbUtSdy41UTFJMWdIa0V2Y2xCbGFCOFJTZkVRLnJXNzBJOHh3V2FkSkhVSEpkYjNILWhrZkNyS0RtTGlXaVJkX2Rldks1S2RnVEhSamRKVDZ5aGFHdDE1U2FRSlQ3UW9xRExyc0s5VmtVXzRfQkNVcmJVWjJPMjc0MUJzVmh0SzA3cFQyUnh0VGZUV0w2TnRRNmlxRDBqYWNpMkpqbGdnVkhpMW5oQVdCVXJsR3VDNFAxeHFibmhyMUxNenkyS0Q2TkRDTW1nWFpwX2o4TG5rNV94MkxrNElvejMzU3Z3V1laaUczaGdmd3d5LXpuTXJCUTQ2Zy1vX1pCd0FGLVBuUjN2TXNabjM0ZkkwcTRrcGNnOFRZa202VjRidllZZTRZM3ZyTkZCSXdiaWQwNFdyby15ZUQxNnNBMnhpdWhySUNVcWppNFNOaERwSjkzaVZpMnd5TDZ1T3otZXZoeUZLOFhfSWxtRjRtTHd1Q1doeUNobmMyX0ZZbUEwa0sxc1J2anpXSDlaNFQxUnMzajFKT1pfTFB0dGM5TlhpWHVnbnRVVkJVS2w4UXUybjVlNUtLSEc3TE8tU0NpMEFCTElyNWM2dzlZYnN4MlZhSUxIQ09FTFpCVnRDdWZtSjRrdTJXeG01T3pQenFvb241WHhFZzlzSkFOdUVoSHZxRnZNWkxHVHFIUjZfb2V6SnBZdWlMX3lOeURkVnF3NFpXT1dfM1BlcGdCLXFTVUNRd3pXbUJFYkh4Zkk3RlpWOW1WSndxb0xVQnBOb1ZDMlAxRWk1MHhXdzVteGpnWnFaQ3p5SlpGX0xxV2czM1BFbWVrRUFieG1pU3VvY3RLLW9OUVJJT3RWeElPZldKTHlFOW1GVzhTN0x2dnRNYmFFQktRUUJ2d3BBd2tqNUtUVTdZcHBaSG05UkFCVVR3UUlJNWtWV1pEcVFLWExQTGNVajI4T2RFd0RBVGFUM3VRdWttVjRSR094c29UZDRadUdGTmhMUUtmRWpMTnNtV2xxQ0ZLREh1b2gzLTBheW4yNENHZHJfSFNtOEIyX2xrWnlhdmNpZWJRX1hSaGowUnVWdGlMTmUyMzNTT1VJWTFjYkRRbHlmdXF6Q2RnRW9aNk1fVC1GQ3F5c3R4VWtONjItZzJ6amZCVG9rTUdrcU9kWjk1ZnpnTXBXbTFMT2tsVmJGcUwxQ0RpWFBMajdLSGppS0lyRnJiTm1UNlNUejFmS18wU0RURHdZM1ZJMnRKZmM0Nnh0aXZqRDEwUHl0WVFUYWxwcHBOTXhYUGZ2QjZ6QWJQSzRyR3ItQVZLRGhXVXlfbk9HYWdSZEZSOF9YYTVxWi1JQ3JrV2ZuMDNjME95VzNMNDFVQkprR2ZrT2Z4VlNIb3dmaXk3c1dxQkI5eVB5bGtSV2tyUmVrQ0xvYTRBQkM3N0pWRjl4S1FhYWVsZkQxZEU4dE9nSTFibmlJUjJncWlxS1I5X2hLUDVmckdMTXItNHZkNUNGWmhFNEFNSDh2YUdsWWg1TmNOcGd3dXNqMTltUkNxdkVVYl9kWFVQb0o1a1FuZ0VoUVdXaGlzTUx2MWtPVHhMVVp4bURBSnFVcldwS0VKRmlTd28xQUFqVEw4SjZ4UGZNQnFJSW5halhOWUo1THUwRHNZODM0X3p4OHpQVlpzdkJiQng2NHlGTTlmXzNNUXNFS0g1TVRzUFRHcW5BYkJqc2tZOUZaVTZlc3JrNVlHTk9iTFBvY2I3S1lhVnBvMFZVYjBlMVZzcXRUckJJdm5DZ0YyZF9jUmZacDQzTmdDUC0wbk5uQVNZMkhGSTBud2Y4ekEzdHZyUkJzUk9PSE9QbUN2M1FYc0htaWthT3Ztc3hsWDZQYktjUHM2WWtXRzc2SkF5Q2ZGREZfZ1pTa0RzWHV1TE5rYVNQeGprNEtZY1FrSVlTRjhpdDJSUG8wa0w1TVp6VVFsNEVsNk1aUGd4clhXV3NBM2pTQ2RfNmZDcGE3NjlJY0NPU1JVa2YtZlUzMEhJcVJxa2dvSFY2SUpDY3ctbWptcnJ1V04tcF9Bb1IwWjJrcHlZSEVta0tzWVZUVXE2Y3VyM2V5ajZMdDJXWFdWQ2dwdHg0OGR2Wk8xU2dwTWVsUTFsZHVvWEpVSF84d0M1NnFoQnQxOG1TdnJKSGkzQ05vaGJUZ0JHUkdTd2wzWDFOTk4tNFh4OUl3emZOak1PZFR6VGpMRVJLUWQyUm4yZHNZb0FoTTg0Mlc4WG9rSjUzbzkzb1pqell5bjJaOUwwNWlYS25XeEtTcmVvdnRCRkhWVVpNc3FnNEdBaTd0WHRzSDlLbl9FTy1Pd0t2eF93dERiU3BwQ3FUdHJGZXVjbmNFWEtPcGtKTjR3ZUxpY1dkOUF1SGoxb2pnMHNTa0xOTjZUSlk0SllXQ2d3amZKa1JMT2tnYmhMWlcxYzVMSVNROWN0Wkg3T01DR2JYWERLaFRiX01lYmNCWmFuTmxqb3VieWU5YmpmSEIzVDBvWXNJd0gtblZsS3luSG9ZTnZhc0t0bk02bko4OFVPdEc2c0QyYlJGejF4MnQtWVlrdWtnSXE1Uk5zQmx1cFdPU3lpNno5UWhOMHpZTmh6dmJuRGVEcm9yeXp1b09UbEZ6cmo0ZTNmdk5PeXhwTjV6b1Ribm1uamsyM3JuTnNpWUJvWTNTMGFqeEMtaFN3WG9HY3I2UUZ5Z0o4S0RkLUxVN3hSTXJSQzhkZlRyY0ZCT2JSUEM4NEo5b0dvSWU0ZmE0YnNCazA1Q0tfUll2Y2pFTGhydDZQQmVQVEUzYmdNVGJqQkZyZ2lscUVQWERHQjByRTJWMG8zbDgycl9ySkg5OTVtd1RMNC0wVTFfTnU0aG1DVnhLN2tDNWhCa09CZUN5LS1ISERmU1dKWk1SUVpaZjBIRmJRUkNlMW56bV95WWNBcDBlYjNwOU16WnBUdElpQzJZQTUzVnZsYmIxNVNRYkd3a2ppWGI2cmQwWHRaLUdxNU4zRE4xRl8wOTBLMzR1Uy1jdGNaOVZMS1F1UWxYbUxORE8tTVFMM3dTUmlUREZTTFhIc0FMTVRnU2JxUEZJUU5LcUJJcjVrVUtMTEg5YktROXdLM0ZlS1NHZGV5R0xCSGlEV01vUE9MSDNiMFdJdHdHMDYyRk1pY2hoYWd5UHltUzhBU2pmaEl5OUpsb2dyLTdqUXlLbE02d3U4ZE5wTGtGUFBTaTNOaGtVVUF0NGVZZFAyU3FMLWJ0N21TMnVVTXFTNFpKTC1PUkVZWTdwWUozZ3ZKMW5QbzJlSnFKcEZ4NDdTcG9YVjMxcUNkcWQzT0FYZWM2bWNWMktXZ1A3X2htalpuT0dxWHozbXdTUmU1NmN4UzhoWEQ3TVBXNF9QMWdULUswWVdPblVWcjRXR0tGNnFqQVNTQktJdFRyczZqS0lDS2RJM3dIekcxQ1ZIN3lQblhCSktFOXlQU1UyR2drY0gwaTlqQ1RMRkxUWWlRa3B2elo0QXdxYk9wanBSQW5WcmJrelIzQy1vY21JOHNZWjNBQUpoZWNtdUVpUV9aU1NrbmQ3cnU1cmRYYV9hNHRoMmp2eEpTc2tYWEprNFJ3cWFtSDR3NURWT2Z5amlydmhwLXhyekxwTHBUdWhGUmU0MEd5cHdIeGh2ZHJjUW5PLUdpN0dMbnRIX21FRDJ4ek5ncXdrQ0VMRXAyWXcydmNjR3hRLUpwRGJNWHdIdlpKbnhydFdDZm92WWlLNkZ6azFEZDB6RUlyakM5TE4yNVhJMGtOVS1sN2ZTSTd2TVlxZUlvSlo4eklqRDJwcHEyVmh6U3dnUGxHc2k1UTJvLUZndHhFUFF2LWJnYWRQYzVLdkR6SWMzRXJYU09xdjRyOXVKNGpqcU1va1JSUXdjWkJ5cFFkUGFVa3c5Nk5DN2VCSVhQRW1qZXpEcnBEZ1JneWxjTktkb25HZlFqWENFdVctS1U4ZnQ3UHpLUUF3QnRTYVM3R0wyTUk4U2tZalBHc19MUFBaQTM1cC12ODBzOVl4VXBOR0xhdW1KLUZtMGRVMXI2OUpXVWVBelVqdEdVd3N5cWVWUzBJbUJieHk3bFpWVGNOVTN5SXBSRFFHMklranpsVzVkcDFqU25yZGZUdlhrd2EzWG9mNWEwN2o2SGZwblZNWnZwMW0tMldCSlFPTmFkZ29ibmliLVVsV2dxUFY4dVBCSGVQbWUtYnNyUGppMms4ZktvSlJLRUZPdGpvWTdKMnhpZ2RValpoemtkZ21MQUtlMnVpWUJPUVpQbTlUZDJoRm16emEweklnOVRjdzNTRGNtZDlHQU1zdFZJN0dISnBYd0NqUzA4YXJfOVJBSVpiTWo4U2dxTEM1QXFqeG45VnRpOW5aQlV1RXF5ZFBmYTJzckRzaEozSF9oT0pncV9jaGw0V0tzcHpqZmxqNDd6OTZUaXZVZkc1WWsyMW85NTVUZnpfVkN2QmQ1Zy1DUlVvbjIzY0U4Mmc2cEFIN2pKSWh0YTNfMVJjNmI5S09XcnFlc0Y2a2U3N0Q3M1FreXBxUXFRV19RdkNtT0pVMEMzWmVWeVJ4LTVEa2ZNY1V5aEt6SC0wdHM5dHp2THM1RVdtdkh4MVFXRm5FdGo3X3MwN3dtWjZQOGtYZUVINDBVTU5MNjJmbEsyS2pvcEtweGoxUzFqZDhPbGtPVUtLaXctLXFqRkN2UTdlaTRfdS1KY0dOUXZIMm1OQm1QOFE0UG9hR3I2cTFCekVjV2xTbUlVSVl2Um0yckFoaUkzYmlDU0NZZ19Rb1BfOVRLRHRlZXk3V1hfcnhyN1NqeElhWEF3emtVMWItUFNBYlo1V2lNd3FhRE1XeklubUNaNTFwZ3JqZ1B3dXZ4MTdDeTdtTk1GZkxuTGppZUdoZ0FoUzhJRWlWM2JvUXJHRloyczlaN29FWHNwUzVPdFBGX05WbnFSRnVnT041aFl4UXFLdmUweEZ6YWFLVExvNU40M0pFM0RlQWtiMUJvVm1LLUtnQzhkNUZpMHB0YXp2R3E2MDk4OWxJb252UWpNcEpSSERzb0J6Z2tzSUttMzdwZldCYTAtZ3NwdEhGU2lvR3hPd256YjhrRVVseWpiYk02dUpDVk1vcDhxRjA1ZmdNSmVRLVVNZEtpeXNKbXllcW8ySDJ2cFdXUGpQS1FROVQwbVc3RjE3cVZfNk5uUXZXazl1Zmt0NmFFaE9MdXNvR2JjQ2lTRHdHMjNGT3RMYm52RU9Xd1R0bTBHMXIwYUlRcjhtM2xac1F3a19vdXJCTnkzVWNmWTJuZF81cGkybmc3WnJtM0NDeVdBdGtzb2dOUzQ1c1NFSDBJVUVIem5vWXMtUS1WM2ZneldFODY3R2h1OHRNQ2M5Tzd6bzhpNHZGaGtjV0ZBMXJFU1dXQzZwejk2QW1mR1QxY1hRWUNUZjFxcWJnM0VFa2t2RVlrdGdSZGE5eGN3MjJJN0ZnLWItOWNHS1NnZ2dYUWNfMXlHMkNrbFI3QkJKMGVRc3hZSDI0eVRSTmVQbVJNR09QZF8xRmRaQmszU1liUTN5eEo2amhzc2VyLXVnRGJUY0ctVzMyNHdocWN6RjFyY3JpejlSZnptTTZCZFZJOTZpM2Z3d2VUQ1J5RWl4YVNYNVI3bHh1b1lWZElnaFhZNzNUQWZzNFVfWlA0Nzhobk1qMjZGYVJPSFZyVjlsNmtDeThwTlJoeTVOUVphVm43TkVXNENGazI0a25ZLWo2ZWdXakdEcXowQUtDSFE1TG1xX0R6UzF0UUIxOTN3bnh1aXJNc1lqWHFBYm93eTVGUTcxMmlTSS1uRkdPTzhBVFhRODlyUDFvM1J5cnZtTFBoUEZoWEh2OXVpYnhkdDg0MFVITlJQUXNGX3c2WVYwNXNLdndpUjZoZWsybkFtbG5iTlEzdG1VR0pBUGxOQnN4WmlUeHFNUlRWWUUtendKQ19kR3N6eWU1RWFzcTVGRmVqakVLOW9XOWMzTlFlcVdWU3l2ejhza01ldEZxNEs5TzZFWjBGTWI5TEtmdXVndHVkbGUtdDVNQ1JPM0pGOThIZ25uVGFZVV8yTjlSQ1cwRUhkSklYQldLT1lvN0x2bEZmMlgtQllWZ1owU2Q4MkNiOHhsSjMtdmZzR2ktOXpNTi01R2I4c3NxdDgtR1BIZ0dkTEthdW5ObVVmcGJIRzg1MVNweWV3dTVxMzJOSUZBT013eUsyZEoteVpoa042aXU3QWQ3TWxDNlhEZXVRNDh1VXV2dTJCNXl3LVBDR25MVlptLWw2LUhJa002QWVOYzM0alAwOTRSazlFOUo1VVoxczktOEU4YWw3X3AzSEhfankyeDRnOUFUQmlUalp0TDJkaVBDYkk4dTV6OTZzMzl6R2ZtelpjYVB3MGVjdVprSWt6ajRqcVdCVlNkd1E4el8xVkdhbWNGUU5qTDN5OHpvTVlqWmNzVXRHdkRXMTc2RWlXSjJEb01TNFZNSFVGbktEaEFxUDZTcTJZczFLYlFRWkdsY0luWW1rV192b0hIV2lpVlBxZFVrdFlwbnFaQzVweHEtVXJ4eFJDRTdXMkhfelIwemlVOGwtekdjS3liUnI4MEs5TUFvWW8yVl95RW56TDhsdXJBRGRnNlZkUlE2YW01T3c0ZFNZZktLZmFPNUJPMEFTc3I3ZHhJNkVsUGFrdFNFYUNLVzROU1dSb3pudmtia2FmanlQS3oyQjg3bERkU3RJcUlFdkZKQWMxLWtNQTRFYXdLQ0I5OTJPRF9BT1F6UjA1bjAya0U3QU0wd1E4N1p5LVJMNjM1SkxZbmFNZnlFVkEyV2RkMmYtNHdyRm1sejNGbDZ4OXRKT2I5eTJVUEtuYVc5QjNpUW1pSEl2YVFkRG0za0JvQ3p0cmZOa2NrdTYxQ0JXQWl3M3F5aGVUcVNTVmR1ZHNyNVZuaDRYU1VJbExMWXRucmVMMnNyc2ZQOUItdzQxX2lranUzSko3NDJQZUtUb3hBR21iWGtjVG1jRWN4WDdQM1FTU20yWmtwd1I5WndLZ2pfVFFjLVdhRS1zM0pLbTI0bnVNVmp3YldYaFVGamEwTGhXUjRvTTF6WUxFRkVkc3dBLXNpYWtHOHN0YVNMR1ZMNlA4eENQRlJNTXBEcktDbXdiQVdQb0Q1YWRBTGYySjg3bGxmcE1NXzU3eFJGMUxyQ2hROU5KNnlGRDVfZFFndWpKV2JlVUxaY3NHODVLVER0bmM1T0JTd3NqQXhBaURfNnB0Rl91LXhQdjROLXVKQzhXaWFIZmM1RzBEckpoZzZLV1drdVpwVDdZLWs4RmZaZDVWdnNfQmF4RjlUODUzS2tfSHRUTk9iYm5sSFF0NnJUNEFvaVExLVhqcmFHOXFjdWlKTThlWDFmMTNxSi1jaVNxeEs0MXpIX0lxQTJialViWWw1bnpPM0lOSURhSzBZeXBRRlp0bnFUWU5zX0VvWE5BZWd5aHY5NzIyMmtYS3pMSUc5c2VjYVlSTE1nSlJXRGQ1QzRpdmdtVElORUdyZGV0bUROZzFaNU1NdUtnX2tQN2MyMU1MNTZCME9rUENXTFRLMnZwcURYby1VSS1IeU5PRnVIRFpVN0V0YVNjR1VzNjJNcnpvekpWOHJaQkQyaDMyU2xiZFRwaExQRXZaSmdUcFRYZ0JialZSaTRnOTF3bm8zbkpkVUFablhuUllVOWJuaGZQSURvNGFlcnNpQTVQZk1scUJsNFBEclppR0lqMUtrdWNTUzNlb2NYSUZBRXFlSF9yTjR3cXRpc2xpNXN2a2Q1c3kta1lfYktlYWl5bXI4dWJoZ2g2bi1zd0U5U2hXV3FfSlNqdmk2RFltNzBPWmxvN1REZ0dwUDByb2FWZzZrLW5HVlQ1ZGVRcXlMdnJ2ak40R2Q4dHp2eGtwVlplQWIxalNaYXJKblM3T2xsVkFjZ2J0TnhHSVNkaEpLUWM4WWhXbFE0N1JFekJMalBXV1dtUXVsNkxqVmdGTm5IOEJqNngtRW5jc2ZDYkMwQlhCaC1WR2xwS2lPUVRpRGhPeVF4LWEzdXBGc3dlVi1OQnd6cndDTVdGczVOT1ZwRWFpZzJCV1NjTENCRXNGVTR1cm9jenJ5TTl4LVRpRGVYbmVKX3BTd1hxaXNyZUxOYlJTa3pqZWtYVk14bjc1U04wOUJhRUNaSFhTaDJOc2dETy1jLWlSWmhJU0cwYk5NVlY1c3NZMDRSZERib0o4UDJmVzExTllRQ0h5ZDRFSFB0eHI0YlNxNlhUSFduVzJkeXFtY3dfMGxoQ0Z6bmdCbWlWRWxsbkhSVkpUX0hpQzhJYTNxUWc4VDhaWElwbTVIQ0dGLUhSN1d0SEJGWmZ5Q2VveV9nSWF1XzBjMjVlMlZ3YzNEdVpqQUdmdUZkX3FLYmRjZWVyUmVGeFZ3NUhlMjhPRGk0UkV6Q1hBbkZXRHNzb1VwTnF3cFgydExfMTZidEh3ZmFoeUxXVFN6V2RjbVNpaWdZT0I1bnk2VnpEUUJXLWJtWXFtOFJoeVBhM0h6VkIwdE1taTdQa2pZMS0waHBkckJ6WGNCa1pPZWRHTlZfR0FjXzJ3Mzg2WUt0WGRfa2hlcWQ1WHU5UUxNNDNkSGpUd0E2RTV6ZDc4NVJMTGRMZjhnVGFxM0xaNHlsX3ZXV3R2cGowVlJjenlwLVRXN2FXWHFwandPUm51QUtfY0dMMmotM1BSeTV3blJaNWZqTHpYSE1IZzhtU3duelhPX2RPd0JOcXotLTVZQUhLZ2wzMW1wWFNQMGQ4NjBIaW9EWFVLUHdPLWJPZWZ2WG9ncGhkTHNHLVF6aW53MFZLWlhqQWdzY0t3ZmpDVDJRMTBIWk9sYVZNbUdRU0VPaG9TZnBZQTZhMnpLVFJ2QnZmdE1pZlJlRG5YVWQ3S1h0Z1ptSEZuYWJwNll4MDVYMEs2dnd3YTktWlVGZDBJVGRtNGRjVDdiZ2RkTzE3TklwbEVua1JpN3Roc01Wd29ERE1hbG5IY0NsWW5DSlgwZXFzZncyTEtqZ2VxY2dPMjJFc1hNYmVsNHNYSVhjejY3dE9TcDlUbXU3dDZHbk85bEVwWGo2dEs5V2RGZXIzWUdWZTRwOXFLNHFmN01ST2NmcGZqSjJwQmVFOUxXMXZERF9RTWptRlB4Vi1PWXNSekMxWmhGTXNYSmZDRExCUXhKM21uYlFZSFNFcnAyalk2MkpPbzRlVkVQNFdybk5zS1pxdUNuRFNISG5lUnhnRE9PNkJKV3BMUWxsTXJNS2tIVHp6Z2w1N3EwMjFBSlF2cWU2Mkxza1VoOGtkWldmbktoeE52NjBLN1N0Zjg4TVR3VFF0dEZiS1hLRy0zN2FCVEZENHVPN2F2bElIcVpaU0xfUk12OFZrVlpRNkppXzhxTC1FZGFVS1FuamVJRFNfeTd3MmFmYXJLT3J4UGRaWm91eUxJTXFFWE54MGF3T0xRdzFCdF8tNzV0QkN5bm5IWW1yeE96Um1Ub1BDdEpoR3RtcmNNMFFlMlNZcGZfRFVYZWhNdzExbVU4cnhvSVVSYjRlMmI0QjFnVWJHRzV3dUxYRVFEUHYtNEZsYl9VM1RmSjBVREJLcFZ3enRyam93cG4wVUdjaGNLMzhuTjA3M1B1Zi15NW9Vb0FNakRMMlBoVkN2NG96QktuOVZfYmRyaVBxY3hrQlB2YklsQ1dkSmlDdENHekFKaFhIekJ2Xzh0YlJGRjFKS1ZfVHhTZW1ySzBzQVRCVG90elhEQllRUkRYNl9vNzdLYUJlMTBpUjNKbDh3WVBueEktMHZhMWliZG1ScFdSOUhXR1RNdmxfVkZkZ2pkNk42R2k2RlRrNm10cWR6RFJ3aDFPRGJ6aGVkdU11TlVYa01YcE9ua1QwR0g4V2ZMc1VzbUppY0VKS0NUUWRwR1FRLkh1eFU4NVVxc2dIYUdsaHRrcXJMX1E"}' + body: !!python/unicode '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLlczVTRGdW94ZzNjcWt1dDdtS1BIV2E3aFZMNjlPcXVhUElMSlFLcTZ4SXlSeGRTQ2R3bTJlSVBXVURDX3QxajlwbWdFQ1NudWFPdVQzcDdUZ0NKRUVEZWF5di14bW45akNyTHhIaXFweXJVTXB6Vm9uZTFQVjJBdWZiZUZORTZ4Y1NtT3l3Q2c1OVAwR3JJeENVZGEtYXBNYlJ2YmYycEJxaUstYUd3bDZWQ0kwRGpXMDZWc0ZUMFV2TWRDbXFHTk13ZnVQRUx6WGFnWUd6cEdxaVhhZFJkSUMzVlg5MURPeXNybEtjU2t3WGZIaklEVVBzQ2FYejZfV05sMW5fSUJzOUdfeXZnZ2x3MlhrRDBsclh1cHdfa09LdmlUOVl2VGF5d085SzdNYmFZU3BncDRVN2tZR2tHcS04ZlZzcUd5Z254M1lQdHlaUWxBSVZIZ0JJQnFMQS4weFlpblFiMmJyYVdPa19LSHZMb21RLm5IZXJPc0FCcTV1bl9YVVZwR3dWeGIzTGVYSjB0Q0R4WWZybWRSZ0hoY0NtZGZLOFdHeTBKbnZZa3JoTDdtVnJrZ08zZjJVRWczUmd0TklWZmdEcTVtLS0xeXNubW5ncFF1X3JEcmZlWld3cENFNkJjM09RbG9sbjhzUjFvTXY1SENyOW5uMVNxdThfUnFSZjhXRHpYLXBLRzJ5ZjRva2pTbTRlMmpfb3Nvam9jMEQ0SlVDYXVGSGtaaW1Qb3M5dFdvdTc3SmtyazJMX1hTbER5UHhRTnR2NkVxTUFaUnZOT3Y1T3NlR2ZTRUVKSFozd3VqUVRaekRxbldvSmVCeU5UblFPSmh6NUE4RG5wbENJbEdabHpYbGRxeUY3dGRNdnNjZmFQdU9wekh4bU5Ja0lucThJNE1IaExyWjFRU2dBODhjUm92aGE1UklsLVRhcGNCNV80OUo0UndENFRSQVM1d3pMTjR5TXV4YUFSX0NBX19DTjJjREtHcVZPdUM2bXQ1YkZqMmM1SEszUWNBVUYydU9qS1h0am5wcnM2WDhjTk9UaW1TTkVBLV8xWGhDWWJIbXBieC1nMDdrb3hlUFI2dUh2bmh4bmZZVlU1ZS1mR2hzRkxTS1IwNnFyVUw1RVhDZDQ4S2JqaFQ4RWRwaU5yMWU4bE1UNjB4NDlFcEdFN2t5SmRxZ2dVVWV1UWFxa1ZQOW5DRFdoT05uZUc4QVdJY2ZOcEViT0E5QVZ1eHk3RXFFWGxDeGw1LWJ0anZZUXJGbW5WWGgxS2FDdnYtSVNFcmxiNnlacVlSQ015TzJORE40REFrM2hXQ0xUT1JFN19xdWdmMFN5aGtTd0hRLUk5MXpUSThOTzhYQXFLYXBxSC1FOTRsNjk5Ym5pRko0M3A0QjdJT0NRVGVXTW5tSm0yWjBlTVkta1hXdG50R3d6Q1FfXzlVZU4zVjlsUmYyRUZPbnJQRXlTU2s3bktaM0M1SDFFYnladlJGRlh5R2JsS2dYN0JUM2RaU2ZXUklRcWk2b2dGVWZUbFd6Nlk2S2p6a25BOXk3RmY0bkpWZlJwMHpaWWxTVVdZeU5Nek5LdUxxY3oxVlJDRnpJbFhGRmN0UVhWQ3RVdzUtXzlUSTB0Z0dCMWtmc0pHUldfckNVdFJyNHBDU1QtS0pTYjFteV9za05oQ19lOFlELVBLNnhRTGtzR09RSEdfc05aX0VXSEh1bWJ5ZlBoTGIwNXJJTURxVEtZa2NfY1dVMV9tcEpSOFhNbll6VGZtemNzMHAtYU1sV1ZBWjd0cDk4bWJ0VWNkcmVFbk1TZy1lcWFXaU9jRTdrUXQ3cUx3SmxWRGhLcjBaeE55dExHVVpIWGlGOEZ4bkhuZDJ1OGRDR1FON1ZBT3l1MlVCeS1zckgzVjVfeWFMcUdBak9rWkFFSTVhWGo1T2ZNRkM0Y2RZUnB4SXJxRloteVR0bWphRmRwZU5jaHhqRFNqVTBvbVZ4LTF3c1VxMHpWNVJFQWw1Q0lpSmxTSWdlU0xhVUd3dndIUy1INXpMdkhDMG5jRmd0RDlad21ZQ05hMGUweG05MEpnTXllUkJOWmhTNl9FdnIzZkpIU29Uamdmazd0M1BXaTNPcDB6MFgwWVRjQ3BnSlliZUQzZExnUmVpM0NjM2VGLVZVeDkyRW0yYnhOVHUxR0FldzRUZ282UExzdHVvSDZETnRyTjkwQjV1cTQwQXhLdnlwdHJDM0RSMEN1SVZDTnRxM3dnOVJ5SzdCUDBWMk1wQk9uYmNMYlVQOENxMWMyXzRORWdLMFowZVdYOE9vNkR2Nk8xQTJ2UVRaQi1SSDFKVmlmLTliM25RTWNrbUE0eDl0TmFCR1ExN2hMTTdFUkNramJJTHgzcE9kSFJQbnFnWG1hX2xHaF9rclM2dzFQejJoeUNtd29uR01RUEVLb2ZyVnZfeUxTdGlWd1hYZWZkT1kyeTVkLXpjS3p0OWlmcHRJb0ZzalM4VEJEQnpWLVEtZy1OQzE0SnFnVWhaNnpZbGtqcDFaeFZXMkpZQ2lMTTN5bTd3OWdiYzJ0OWt3ejZLUFlESGtzN19MOGZtQmZldGwxcHJ1SjdxMVpWZVNKbHJtaGltdGNSay1LU1dxWmJkN1AtYTRKZUNkOERHXzdmWWVJUG5XX2hYeXRxYzBtSFJlUVJSY214UGpLTU5OaENKb21TZ1ZLdi1LcVViNXcyQ0gxNDlRdWFsLWRPa0poYW9INkFMdTJIVUpaRENyTEpyTjdKZlRlWVVyV2h3MDMzRXIyQ2xRRzh3a2paY0xwS0IyaGhRV2RwZzA3U1EwXzVGSElmZjI3WEFGZ3pmbEhBSTBaOE5hTUx5RGRaN1JuWlZWczdmUHFmR3hUc2czcUlkV3ljTVdZcHhTdmlubGlYSExjMzRjWHdZTkFGeDlQaXplZ3dJWWVFc2w0MW00bWJueXl1Nmw0YXJfVWx4TE91MkUtWTg5cGwwb3lCT1EtUThsa2VYVy1tY3NKcGViVW81OGN4SzhYZ2xXOXA4UkE4Yk5PZHFwMmJMZ0N1SGZCQWxXc21NS2JRYTdHV0hvOVQxaVRENWV3YmRCaHpIN2x3WHJRVTN6YVZQRUpqZWw3QVVNWnJod2JLM3VXR0ZVeDdpTHU3Z1BoV0dLMlAySi11cWFzQ3ZLdXhqSElndE5hckFzOVlVYnZsWUloeGtOWGZZS0dCVVNHZVFoR1B5LXJpeVFDcll2ajBmLTVOejJtRzllazVDQTBIb3JFcllsWEh5UTRZZDBRZzdCN1pOR3dmX2tHNVZKOXczVEhzd1NNbjNTNTV6Rl9xaVpBS3IwbVRHbC1LOWxIU21GejNPMk02RFVzeVpRM3lXaUlqVDE4RF9ieW1idFAzdFo1WHczOHJKdVhidGJNRGFONGdwalJwZ3lyU2gtbWtfaDNlTmZ5RWZVdWN6UXdZZzJIUy1IN2N2WGlkUjBRaTJobmpfeWhRNDM5cWpSNjQ3Zk81T0lfbmVGdzU3bkdXLUJhV1RTbmdxMDc1MkxkRi1tVU5HZktKX3dUclRoWlZ2aHI2WnFXQzRIR2Z2dDU1WVVydVdTU3dwRXVsWDlhdGNiUzB4cXNsSm9oRUZEOEdhaWVQeF9kV2Z3Q3ZNQ1NWZC1nVm5oYTNSRG1yeTJGSUk1MGYwcGVGZmh1ZTFDdjdPaWxHTnl5cXk2S0N4TkJCUzFtRXR5Zm5IcDhJZ3J0X2JUM1lTVU90aldGTDc4a0g0OUlEY0puV2t3MUJQSWVJN1NTTk1qRXJBeW5RaDhwUy1XUl84azFDZ0lBUnc3dktMcmdOQ3ktNFEyUTBpQ0ttSWl6R3UtYUZsOVFvMHV1YU13WlRrazZCR09yQXFacVNaWklMazlPTFFWaVI2QVVNUldqakRlYVM0RS1wQkFvRWludWpiV2VCU0pTZ0NWcUp0U3RHN2t0enlGc1VPNnk4VVhJRUpNdzVsc0JwNjBlNTJSYmRaS3E1YnBSTzNDN19hOTY2NkRvcnZMbWlSVFF1U2hGellsSFZpSTdFZWxvNW05RXQwR0ozOUlhbVJpT2M0YnRzWllnMmJjRmFoU1NoNWxFQ3p6XzFnSElSakh2SVMwUkR5azZ4TDh3OWxOQTg3TXhSamVoajNkaERjUzlIcjJ0dEVDYlI2ZVZRMVBJY3Q4R1RqYlhzek50OUdGVkY2cVlEdHNFeWRRTXYzQVFYV1VOcjJXbFFtb29nNC1Ua05mSEs4Zl9ONFhUa1RHWUh0WWV1Um1CZThqc2gxa2hpZG5NYjdWRktpUGVXRWI5cVduZmVETWpzTXZLMmV6Vi1manNrRnFNNWMyVHM3ZVVHeHpRcUVlLTJ2dTlQNmxsM190MHVyb1NBTlNTeXlLZzRfbmxFOERLYTdRVWtDQ29mQjNCTlVQZGlCcEFBamFjVEtweWlnSURRcEVlUWhyV0JTQkItWmJpZkJFNVlDMzc3bWdzX3I3TUF4RmxDNXYzVTRpSjJaTVk5UEJLUWlrWGhfMlo0LWRFU2NIMmNZeXVwcG13Vk5Rd0kwcFA4T2dfRUxxY3hJNS1JZ05wMlB1NnpJOE1Rd0taSEZZbUd4b09Gc2tfZXlxVU5nRGZoaURUN3Rqb2tSa3lYYTNIOEZTUExsRHR6V2w1RTA2QVNKY3Brdjd0TEh2NG51TS1fVlBQNzFXT3JqTVl0b285Ym9RM250eWVGRG1wTXI0bDZrWE1GcmYxOWZBZUtHaktSeEV2akhnNlU5cUN1a3VwZ3puVFdJZUM5ak8wV0dSQXZ0QTZ0REhjMExEWEkwanJLSFNzMDFfSHpOMF8xbEJ5SXRvN2lEWkhYYmM1YllrcF95QzVpa1p5a3JwelBjRTBBT25MSEZuSWs0aWhzcllQSW5jdzlYOGc3WDRPVWh5YUhiUXRzcG95cXVYdF95N193aXBzSEE3a1diUDZZOTJhUHdZTFQyR2RvUkVOUWhBTDNKN3hER0V6aXJhSDVOT3NDV1BmVGYxV2liYkdBVnZTMVRndFlYOGgtUzdZaWx5V0lEandWY2dSSEpyMjdXQXFVWlZFamVaWDB1cjZpU2FOdXo0YjNVWVh1R2tGTFlfY2Y3Qk1XZVFXUkd5Y2ZSVlpLRGxxb19fRmhvbU5hUXZibzRFQ3VSMWNqMXAxS0h2VVR2bEpUd0EzM1JqengxMFkzemY5YnBub0p1OWpHOThQTW1tY1djVmZneUVfLVBya3AwaXJTcGIzVlNYQ24wbHBQbjBWRHlySTBERjZieW55b2xjVk10c1JhZUNoLVBYUnlLeG0tckZCcnVXSGxNcVI3bGRfdmowNk45MVp5bkppdlRWN2NSR21GVHFjeWRqdGw3MVVhTDhUYXc0ZU5YWUZMV0xGU1R1TXFocEVSb09haW80WVI0UzZPU0dzRThTc3BQT2NUVDRkYnVpNGNIZGNLeW9Yb0VFRTJTMTc3Um5jajJMTlRrNWc1RDF5SE9mY2VFTDhfbDZ3dUZTWmxKQ1oxZGFlSFluSEFqcDJ6MHExVmV0eVlfUmxmd2Q1aklsVDliSnlzdDJyTEJkb2xmc1pucDU0MkpGVjU3SDNGb3o4N2dzbXV6Zmxoa0Y5Vk5wamxEQjNKMkMteTFvaURDNlI0NU1kN1BnMnR3RmpTd2sxMlBqUU1Xbk9WUFc0aTlGd3FtTjZhQWN5bGZKNE5VNk9ZM0hVYmFRWFd4Q25JWFQtUlNaRGdLeUZGWlVGYk9kVmZxSDlwX1drTnFkTU5PbW00UlZlbHpvVnhfYmNSRzZxWHZCUzZKUFdqNDhWY2J3SW1xXzd5cjlHWHo3RzhqQXFhWGpGLVFRTkxLXzQtZmJ2dDBTSTg0RGJzS0JZNXFuQ3BRUXpoVE5FU3Rxdmw5SzUwRTduYmpTNW5IOXZ6UmQzaUp5UUFvX2g3SVoyNDF3TmlPQjhRdkFvSi02X21pQUV3bHdTdk9IOXZ2LVplSEJpWG9Qd3ROdEd4NDdHU1FDblZXNjJhMm5rbUlXLVlyR3h4bFNlYldkOTRkaWtCaUxZOUxRQllZYmdDOHRfcWZObG80dEVxRFZ2WjVsZUVwVnR3b01IYlYyR1dEeVA2bzI4NWUzcm5ubzRFZjlDc2lrNC1CSUM3TTdtbENEa2I3bUpWT2x0UC11emcwME5ocVdnd0NZWXRZYmZ6ajFZUmNfOTNCX3pFbXdELWRuUHBEYVNRdE1KVEhDVWNNY0RlMUhaRGs5TGJMSWJ4aWpVbXZGQl9nRHdJdGZ1VjdtdHRJS1RDMlBzUXMzQlowZFRYb3lvcktUYThWdi13VktpZ1pxbENmR2R0S19FYTdlVnZKa3FyUV82MTBXZS1uRzhYUmRaV2VaeGJUZkNyZ3UwYkhreXcxUmFQdndVMVFyQ0s2ZlpRcktoX3JZcjhWM3o4dnBoYXBOMksybzI5ZnJTZlo1Sl9BbnZyUUhfLWJPT042d1M2X0ZkV2llZUk3U1FUbDNHZVVjWmp6T2Q4SEpPY0s2SWVrTTdUTEpENC1JU1FHMFgzMjdoNFhnbTlwcGRhMUtXTlFLVzhQYUhIZTdwcDdZb0k1SXltajJWVXREUGxTaFBDZHFDa2NsZTJ5RERRaHhHRURoQVU2RlVtSWxOck8zaWdUTUx4Vm1QQko2U09vNXU2ZWVjbnJzN3lrZlN2b0NDZTN1bi15RnNLNlBWdEVoSFpHckc4MHJ5cHdOa2ZPTmkwNkNIdlBDeGF3RVdXUWhfUWx5V2JFTzNVaEpOa3luaWlZMFRBQzBBdDhZWGo3QnZQeHRvb19Edy02aWplVmdid3ZhTVVYdk14YWhDUHFlRlQxLUN6S3lWTzNHeHVmVWRzWEwzd1dYWXo2RDA2WmFERXZpSG1TV0h4QmVweUpYcEhkY1JsVU04Snd5U0VRVjkxVHJkQVJTMHJNZGlvRzJrVzNjVkYxbzBkX1lQbkZidjFJal9MQ3BodF9wNnZXeDVtbzNJRHJxVENTMDM5dE52YVk0V2tSY01yaXgxWDdHTWdXeFpZNXFOUFZrUWlvek8tNGE0UWlmS3JjUDh0VTBQYkxaNzhxS3Q5dkp2elV2Q3V6YzJuMEpneXJJMkIyN1lTX2E3ZldkblJ2T1hBT3BPdTR0Ny12UUtQdEJpcWctSWhJeURYLXIzVkg0TE03NUNfVzJHNVl0NUhiQ2FKb2JyOGdldENpOHVraDZ4LWVobnhHOTQ3Y1dwaGFqcTRfaGdvVG1Mb1VBdW5UNmVaNVBfVmc5cTg4dF9xbV9kZ1cyUkxkVW8wU09qcDFXWDBWODNIaTVJSUlTRTljMHRDQU13RDZSNXRNUVNlLWJEdmdqbTNwXzJ1enNyTE96WVctRlhaSlN5SS1fMkUwbk5UT2M0OE04cWswTVZoMThKaloyWUVpcmE3RVdUNlRlZzZ3VlpSX0UycEN0ZzhmNnpnUi14aEtaM2F5WU1MaUFZb1RJdFdhYk9qWlhMd1pxSjk5Zi1qanFUTktPS2NjUlhMOXIwMDJ5NlhUcnBXZTJ3aFZLTmhNZ2lxQzZCZDJGbUhzWjR6T292WXhsQlRtY3l2STJOWC1EMlBqMktnNjg3MmViQmpGREFLYjNMUFk4bjROd0VKbTdNMlh0MzBGYlF0cmFLdzRSNXlUdDByeVM5SGFnV0E1ZDRoSHVqLUZ5WG1rSmVPWkVtZTkyUzB5NWl1c00wMmlXQTZaNjJ4WnIzd3pqV3Y5S3BDOWxZZUdwU0xLSU5WTE05eEhQQmlxNXVrYWV3RXcwVGZPZTdLdHZpTHpRVjhSVnF4UVpub05sUEFRSGcyN2JPYmxsTld3WUZTNHprNU5pcGcySExacTF2UndxZEh5WHJNT2JxenZCWGY4MEJnWDNGd1ZWRVVEdi1VVndSQlhhYS1xOV9rckRxM2lVbkhNRHFSSkMxdk9HVDB6eWpTNnY1cDNwemtUdElQb1ZDa1BETGI3SlJMUTMyU2pJNU1Za3FyR0tDTGY5M3EzSXFJZXRjaU9Fb2g0TUhsTmpLU25nRjJ6dUwyNFEyWnhNdjJJczRYWFBTTWh0dEJnWlNRVXFFaXp6bmM2OWd5UmpLMVdFLWxEdUdiS0NOSm9WSHRGV3V4ZW41eEs0TWlVUEJhMGVKbkMtcklnbFFkMW00Q2pZbVd2RnkyNnowc251MWM5M2RCblJEekFhVnFNcTBRZ0ZpbnV6d0FMWWJNMG9SOFllal9kT0JjY2RUeVloVlVmVGpFQ1JOXzhiazFkTllZMVNMUjl0bUZlRnE2ZU1iNEw4bWE5ZHFCRjNmNVhSN0k1OHJnODlGTXBmZGVLMTRza2xnZ2M0TWRReV9EOTM3UHZabUZrTXZKN1JBU0Z3RExWbEx5V2JGbGdUX2FHWTVtTVdZd0dPQVhZbnduTU5tSi16R2VuMGVOQWlhRFFRUTl0eTQ3TUVKdjFpMWxvVmI0RzM4YVl1a2VhdDZuR1M3UGppdUNGcUNSVTFGLThOd1gyXzNPcDh3MjdUSkZ6TjNtQktGQnpxQUZkYWRPVFJpbFRqRnV4UGk0emZNakhxZUhFa1g4TGIwN28wY04tVUN5QWxBMnk1eEJoczFlM1lSZUpvMmp5Q01sSF90ZjRjWnZIdEQ1MlRXYWt5QW5wOVA1MHJ2c2JpTXg3R3RkSTVKYlQ1LU9EUzNORUQzWUdHR2JTcTFSaFl2TzM3ZzMyWjUyX3hYOFpnaDQ1eTdNNVNDQ1ZyeUpjb1V4ZHNidU1NWFEzbi1kaC1tamw4M1FuTTRtTXNpSHRCdF9TQW1BRmNCQmh1Zk5sakxjbzBnX3ZrOG1IZkROVzgtSmpjblVqMk5yRVlBc0ZUenpnNlNJX1dPYjN4eUhFdWdfN0J0ZlZCb3VWWV9EN01ra05SN25XR2dXdmozOXdyYkJxS0prbVNacEF1RTBFMXY4SGZVMElOVFpFSnFIQmFZUGhWZjVqdDI0MUZpRFUyVkhUMkp3QTFiZ3pyQ29PMGtjMGJCQWx6b1I2M2lWbWdUS1BjTkdrMzJYZ3R3RmNIb0p2QlB3cVg4OEcxbkF6RlJrbFFQcWtnY1V2UjNSbThoM3pkSGhmaEFoTGxaT24tYWdkNTl0ZEFVM0ZKSmFIOWVZbnVKUUd5aVcxS0RwM1RQcUt5N05XQ2hSdGRPOTByWnhVODEyX09fNlZQRDF4YkVQXzVpR3BMRFZneEotNEIzYkJnUGVUSzFrZDBNRW1rMEhuVGtRemVFVm9aNEtPZ0ppemE2QXhyeTduZWNzSEtORzlOeUlYRWU0SzV1cDQyalBKNW1DU2lBLXZ4UVNqd25UWWhyNkpKTXYxM0tNN0hrX3c1UVJ5LW5waWlsXzdTU2d5cWptMUdqTzg2SHJzVm94RzJaN3huR2hlMEh1NExfMFdsSXppVzdaUTVTNEgtTFVZV1A3OWJxNnNsN1RWZWxYaHpCVEtvLWxvem9MaVNoUlU4MWJtR1NSYWxEZ3BmcnNWR1ZIUjAzcGRhY0dZUU42ZWowR2dmMi0tanJtVFdEZ3ZGMHdGeXdKUnJLdzRwdWcxcldNM1VwRHItd0tvbXh1aVdpRXVhcU5MNTVpSVg0RkZScmhCMExXSTZ0LURNMU81NmZ2czhMSFpLUHZCeU9IdG5XdlF0WFhCb2tDRFFRSlpJcjNRM19RN01CdmNZTlNELUpoLTY0a0E2eGFQSTN6MzFuU2J3OGJFWFZ4RVBkVGZhV1RRM2RoemRVbEpCVEV4bzczNzctYWlFTDZBVWlBWmNpRWFhQzJjS1hXY3R4VFY0czlEdXhVQWlvR05YRmE3cy1Pa2xNSGdBSjU5MlA1Qmpfc2ZXNkdmemdLVmhuQXVBODRqTHRUYmFselk5QWc4cmFWRUJxYnhrZVloLTV5MHZsQk1sRzZCa2w4aWFSWEs3LTJLQ2VENFJRVFBCMGxwRzV2YVNOZW00MVEwcnZWNnAxUXVBWnA1YlNhVW9xS3B6aWVCeG94Ym5pUFItRzRGMUR6YXhSS1A2MVFHYXpUR09rRkNMNFhOOTVoVUVmRHIycXVtVUhiZk51NWRTT3daWkRqbTVfTzliMVRyWEcyd3M0YUNUSVk3UUMzS1FDdlJnZWJzTFUtOWFzQlBNTm11Ukh5aUJ6a2pBYkdkU2Fkd1BXbnM2VVphZlE3djF1YS1hOG9ZNzdEa0FFU0FhVTZIakE5YldfczlSam5EZ2RNTC1OSjdlMFU0T0dIT1o0WGlwWUR0UE1RNXVsX2xHZGU1Q2pzQ2pBdFRvTThNTDY0WnQ5cWEtM2ZDTTg3a3dyaHJGcGx3QkdHWGxJeGhFaDE3bDBKU0ZOYkREZEY0d0hIbkdyR3lsd2RLT3BpMDd2eS1RLnZra0FYU3FYZHNFYmRiTDVRNGxCcWc"}' headers: Accept: - application/json @@ -165,12 +218,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault103214bc.vault.azure.net/keys/restore?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/f371da03e196435f88075eca2e30ddfe","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"8dkTcJCcZ0JM383xGLQhD6n08fPHXoacd7nn4GJcYREbvstt7D-KRKLOSTscgPG7eFTwQ-FXW7bOF6h_7FPoSLU72QL2t87rLNb-JBU4N5Bx9_CAc88JLC3mIM_mrWjKUG0biC02w9GFptx16W3ptF3Qcq0hEjvZpyV_LhO-p5iPJffx8Eaxks-VImc_QyFIq_ygVYL6kdXENMNVAmiybe2RCai065dCrRLj9Hyy04NjwsTxBW4PIkr3HivcP9tqEpj2FLv3r80nF3eM3dU77X8KT1o3MSlRmzRPug3tETesp8gcouIQUOHD9KORMe6HN5uP3--UBXFw5gpJQxc2PQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault103214bc.vault.azure.net/keys/keyrec/03ee7dca2ea544e59cb0b18c89526d0e","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"1cXx6yL2Hz8wk3-Xt4l79EF5c7AqhzgufzJf1H7D-E56c77-kms2KF49OfN5VYBLFS9v_8EiPjB_73mDimH6K9j_9JNXCjriAKFSJjLpgiFYe3McjtyezrMmJaXwlXkMOs36hCUEVrz30B0FXWb80zY38TCN1nTaPCcg6FqeebT3HOoElTFYHDQ28nYCeSfPuAofkXJv5T1SlnpKiP04sI632eV1_nx3JBxikTisOMgmQV418sC2GmWlnSxqB_pwfUxxHj_zrytQo56SOkWA0Cng77w6rjd5DDwBndky627nnf5qkyT9K38HLK-JSD9DMS-h_1RKAnSNDOy7c2f4Xw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703749,"updated":1562703749,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -179,7 +232,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:22:29 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_recover.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_recover.yaml index 68110b2b280b..577d4ed4def2 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_recover.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys.test_example_keys_recover.yaml @@ -1,6 +1,59 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault889211d9.vault.azure.net/keys/key-name/create?api-version=7.0 + response: + body: + string: !!python/unicode + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:23:03 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: !!python/unicode '{"kty": "RSA"}' headers: Accept: - application/json @@ -13,12 +66,12 @@ interactions: Content-Type: - application/json; charset=utf-8 User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault889211d9.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/557a49f3e3024f33bf4898b0c41c335d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIJ23YdUgkJa8JiOlkK4BaLqN03l6srA5uHJZHRMk7JDupIJVVCCdyFG4WENbNkgx7bd8nhoKq4nfwDQgpnnkD3AwDX6s6CE8_2Fes7Xr1f87N2aiyYXZDf0dO3Y4kN73onDvSTXiBVJImO6ys9PcrGIqcNQoAZr1g22fFgtQ5uJGseVYchhdBJNVLgkakg4taNv6cafGUkpj8OcEQwakBzE0DMjcuUHiBIPfpQT8s78YdNsVau9v-Hd3toAKbjGnuBwuwzixO7usL1kxjet9W6ooPKImaGkt0TnE2GwEgs9y4KYM6DGSTVzKh89JTAocw8Be3KztbKjsdKOzSYIww","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/479bfa4e48454dd18612535a3be890bc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ox9o3TKLQKzuFxh7O6gTygJxPYNoAe7JPPUq7jEYYDzXNH3Z3UyDaDSp1HKyiVeDO1lbdxEN--V_ZlmnElwl17icndqZeJcRl7hJszjza6uZbSZIMXjwwfPPOF_fuM6EtKlIiS688ujRF9QuHCr40vyyaFlRtkQzu748-X92GWXnEDI2nGM9WdrQ-gYcyXB7SOb7Ref2pB3e6DCTY8xAsHIJ6G8jEl_6s0dsu9nVJ_5BaY2nzzJrXt7f6uqXuogDsUBKrD9fOPN0MRWfJo--lY0OY5NmJxIjZ6og5NsloOAyfk7v7Ny-vIBeW3hC5YYh8Wli7EP7wgpmyhEv6OdfBw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -63,12 +116,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: DELETE uri: https://vault889211d9.vault.azure.net/keys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562686804,"scheduledPurgeDate":1570462804,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/557a49f3e3024f33bf4898b0c41c335d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIJ23YdUgkJa8JiOlkK4BaLqN03l6srA5uHJZHRMk7JDupIJVVCCdyFG4WENbNkgx7bd8nhoKq4nfwDQgpnnkD3AwDX6s6CE8_2Fes7Xr1f87N2aiyYXZDf0dO3Y4kN73onDvSTXiBVJImO6ys9PcrGIqcNQoAZr1g22fFgtQ5uJGseVYchhdBJNVLgkakg4taNv6cafGUkpj8OcEQwakBzE0DMjcuUHiBIPfpQT8s78YdNsVau9v-Hd3toAKbjGnuBwuwzixO7usL1kxjet9W6ooPKImaGkt0TnE2GwEgs9y4KYM6DGSTVzKh89JTAocw8Be3KztbKjsdKOzSYIww","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562703785,"scheduledPurgeDate":1570479785,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/479bfa4e48454dd18612535a3be890bc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ox9o3TKLQKzuFxh7O6gTygJxPYNoAe7JPPUq7jEYYDzXNH3Z3UyDaDSp1HKyiVeDO1lbdxEN--V_ZlmnElwl17icndqZeJcRl7hJszjza6uZbSZIMXjwwfPPOF_fuM6EtKlIiS688ujRF9QuHCr40vyyaFlRtkQzu748-X92GWXnEDI2nGM9WdrQ-gYcyXB7SOb7Ref2pB3e6DCTY8xAsHIJ6G8jEl_6s0dsu9nVJ_5BaY2nzzJrXt7f6uqXuogDsUBKrD9fOPN0MRWfJo--lY0OY5NmJxIjZ6og5NsloOAyfk7v7Ny-vIBeW3hC5YYh8Wli7EP7wgpmyhEv6OdfBw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -77,7 +130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:23:04 GMT expires: - '-1' pragma: @@ -111,12 +164,160 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 + response: + body: + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' + headers: + cache-control: + - no-cache + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:23:04 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 + response: + body: + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' + headers: + cache-control: + - no-cache + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:23:08 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: GET + uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 + response: + body: + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' + headers: + cache-control: + - no-cache + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Tue, 09 Jul 2019 20:23:11 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' headers: cache-control: - no-cache @@ -125,7 +326,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:23:15 GMT expires: - '-1' pragma: @@ -159,12 +360,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' headers: cache-control: - no-cache @@ -173,7 +375,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:06 GMT + - Tue, 09 Jul 2019 20:23:18 GMT expires: - '-1' pragma: @@ -207,12 +409,13 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' + string: !!python/unicode '{"error":{"code":"KeyNotFound","message":"Deleted + Key not found: key-name"}}' headers: cache-control: - no-cache @@ -221,7 +424,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:09 GMT + - Tue, 09 Jul 2019 20:23:21 GMT expires: - '-1' pragma: @@ -255,12 +458,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562686804,"scheduledPurgeDate":1570462804,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/557a49f3e3024f33bf4898b0c41c335d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIJ23YdUgkJa8JiOlkK4BaLqN03l6srA5uHJZHRMk7JDupIJVVCCdyFG4WENbNkgx7bd8nhoKq4nfwDQgpnnkD3AwDX6s6CE8_2Fes7Xr1f87N2aiyYXZDf0dO3Y4kN73onDvSTXiBVJImO6ys9PcrGIqcNQoAZr1g22fFgtQ5uJGseVYchhdBJNVLgkakg4taNv6cafGUkpj8OcEQwakBzE0DMjcuUHiBIPfpQT8s78YdNsVau9v-Hd3toAKbjGnuBwuwzixO7usL1kxjet9W6ooPKImaGkt0TnE2GwEgs9y4KYM6DGSTVzKh89JTAocw8Be3KztbKjsdKOzSYIww","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562703785,"scheduledPurgeDate":1570479785,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/479bfa4e48454dd18612535a3be890bc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ox9o3TKLQKzuFxh7O6gTygJxPYNoAe7JPPUq7jEYYDzXNH3Z3UyDaDSp1HKyiVeDO1lbdxEN--V_ZlmnElwl17icndqZeJcRl7hJszjza6uZbSZIMXjwwfPPOF_fuM6EtKlIiS688ujRF9QuHCr40vyyaFlRtkQzu748-X92GWXnEDI2nGM9WdrQ-gYcyXB7SOb7Ref2pB3e6DCTY8xAsHIJ6G8jEl_6s0dsu9nVJ_5BaY2nzzJrXt7f6uqXuogDsUBKrD9fOPN0MRWfJo--lY0OY5NmJxIjZ6og5NsloOAyfk7v7Ny-vIBeW3hC5YYh8Wli7EP7wgpmyhEv6OdfBw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -269,7 +472,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:12 GMT + - Tue, 09 Jul 2019 20:23:24 GMT expires: - '-1' pragma: @@ -303,12 +506,12 @@ interactions: Connection: - keep-alive User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562686804,"scheduledPurgeDate":1570462804,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/557a49f3e3024f33bf4898b0c41c335d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIJ23YdUgkJa8JiOlkK4BaLqN03l6srA5uHJZHRMk7JDupIJVVCCdyFG4WENbNkgx7bd8nhoKq4nfwDQgpnnkD3AwDX6s6CE8_2Fes7Xr1f87N2aiyYXZDf0dO3Y4kN73onDvSTXiBVJImO6ys9PcrGIqcNQoAZr1g22fFgtQ5uJGseVYchhdBJNVLgkakg4taNv6cafGUkpj8OcEQwakBzE0DMjcuUHiBIPfpQT8s78YdNsVau9v-Hd3toAKbjGnuBwuwzixO7usL1kxjet9W6ooPKImaGkt0TnE2GwEgs9y4KYM6DGSTVzKh89JTAocw8Be3KztbKjsdKOzSYIww","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"recoveryId":"https://vault889211d9.vault.azure.net/deletedkeys/key-name","deletedDate":1562703785,"scheduledPurgeDate":1570479785,"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/479bfa4e48454dd18612535a3be890bc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ox9o3TKLQKzuFxh7O6gTygJxPYNoAe7JPPUq7jEYYDzXNH3Z3UyDaDSp1HKyiVeDO1lbdxEN--V_ZlmnElwl17icndqZeJcRl7hJszjza6uZbSZIMXjwwfPPOF_fuM6EtKlIiS688ujRF9QuHCr40vyyaFlRtkQzu748-X92GWXnEDI2nGM9WdrQ-gYcyXB7SOb7Ref2pB3e6DCTY8xAsHIJ6G8jEl_6s0dsu9nVJ_5BaY2nzzJrXt7f6uqXuogDsUBKrD9fOPN0MRWfJo--lY0OY5NmJxIjZ6og5NsloOAyfk7v7Ny-vIBeW3hC5YYh8Wli7EP7wgpmyhEv6OdfBw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -317,7 +520,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:12 GMT + - Tue, 09 Jul 2019 20:23:24 GMT expires: - '-1' pragma: @@ -353,12 +556,12 @@ interactions: Content-Length: - '0' User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + - python/2.7.15 (Windows-10-10.0.18362) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: POST uri: https://vault889211d9.vault.azure.net/deletedkeys/key-name/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/557a49f3e3024f33bf4898b0c41c335d","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rIJ23YdUgkJa8JiOlkK4BaLqN03l6srA5uHJZHRMk7JDupIJVVCCdyFG4WENbNkgx7bd8nhoKq4nfwDQgpnnkD3AwDX6s6CE8_2Fes7Xr1f87N2aiyYXZDf0dO3Y4kN73onDvSTXiBVJImO6ys9PcrGIqcNQoAZr1g22fFgtQ5uJGseVYchhdBJNVLgkakg4taNv6cafGUkpj8OcEQwakBzE0DMjcuUHiBIPfpQT8s78YdNsVau9v-Hd3toAKbjGnuBwuwzixO7usL1kxjet9W6ooPKImaGkt0TnE2GwEgs9y4KYM6DGSTVzKh89JTAocw8Be3KztbKjsdKOzSYIww","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: !!python/unicode '{"key":{"kid":"https://vault889211d9.vault.azure.net/keys/key-name/479bfa4e48454dd18612535a3be890bc","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ox9o3TKLQKzuFxh7O6gTygJxPYNoAe7JPPUq7jEYYDzXNH3Z3UyDaDSp1HKyiVeDO1lbdxEN--V_ZlmnElwl17icndqZeJcRl7hJszjza6uZbSZIMXjwwfPPOF_fuM6EtKlIiS688ujRF9QuHCr40vyyaFlRtkQzu748-X92GWXnEDI2nGM9WdrQ-gYcyXB7SOb7Ref2pB3e6DCTY8xAsHIJ6G8jEl_6s0dsu9nVJ_5BaY2nzzJrXt7f6uqXuogDsUBKrD9fOPN0MRWfJo--lY0OY5NmJxIjZ6og5NsloOAyfk7v7Ny-vIBeW3hC5YYh8Wli7EP7wgpmyhEv6OdfBw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703785,"updated":1562703785,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -367,7 +570,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:12 GMT + - Tue, 09 Jul 2019 20:23:24 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_crud_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_crud_operations.yaml index a1cbef9dfe04..c4fcbb136ec5 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_crud_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_crud_operations.yaml @@ -1,7 +1,60 @@ interactions: - request: - body: '{"kty": "RSA", "key_size": 2048, "attributes": {"exp": 2527401600}, "key_ops": - ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"]}' + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault9627173e.vault.azure.net/keys/key-name/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:18:51 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"attributes": {"exp": 2527401600}, "key_ops": ["encrypt", "decrypt", "sign", + "verify", "wrapKey", "unwrapKey"], "kty": "RSA", "key_size": 2048}' headers: Accept: - application/json @@ -19,7 +72,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/3aeece01e95c4a10bcba70186796f9a9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"yPsy8FfUg-Jd89YtMlEToePdztBpfHkKD3hJ_EeYTpCQXd64mk17dBFuk6x134_Bfg9i-nLntZnOLFM4Iz22H0bmhS9pq0PXyeWFv0-69OPWD238P2zMWoz4jG2AvDV6KQuiUaVpDv_IetdyUMp54G79M8nT9mR5K9FbxDq2Gc6FmfN-C9CicjFbjcN8hQfx8Oa88QVdmELDsuwuNaR4ZEPqgia15TuIp7uYUoXqV3gtexr_30WnZQHTRUpu98sXXXnWs1k2CORiWTKB0voXYMDrwpzzsLQnK6iism2t5vBDr0tHYSTydDhhyabYcGd-V0tA80LFDCkvEA1WtKYgIw","e":"AQAB"},"attributes":{"enabled":true,"exp":2527401600,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/1bb7bf47dd094b40aa9c050474731167","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"r-JtT7b6aQoFd3zphPJ6UkTUHc3j0wzUXYodlpplJVWbNc0wOkaeA41omIFcMIVekvTmFJrFPMw3NFTwGeO0TrbQvQJtmksfIx3dTPWOG4zlWcnWrHMhSS2m5HZwYQPWFhSHB9VtgBE6fCzosaInazhiI61f1eGULobYGiRWv3fZ8Ez4iF531AC1RbgYT9uHoOCZ6a2LvwBOOEC3vGLJGRy0A9Ez-s2Yek_fy95ZVySOD7J6OSh4h_GC9roVICi0v9fHwGd8MXCRnKzq-lUxrylIsyfXU78PbG4rojGMQswD8oPfkONH45jWSBe2e2SXkx-HiRQ4VzbMKOxm9bpSYQ","e":"AQAB"},"attributes":{"enabled":true,"exp":2527401600,"created":1562703532,"updated":1562703532,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -28,7 +81,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -71,7 +124,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/9672299b3d134abb85fc765021ae4dfc","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"uHCi2ntUeXwKtbW7Pcq0kO1Q5wFMXLqgagc2S1MVREgqz5vUqJhxv1Brcg4Fn_0_FHsMsi3Yq54A2qgb-TWyjI-KyTrUa40v09KVTRwvbO3uCfGpxcvk9PUr9z46WN4c0Z3uOIHnNJEA2cqdoIWCtKxQqfwHabBSgJJFdWeDkEEKr_cRjaVfBDmvsC53ipJ6_iFvnpI-V_nuVcuCbaG9pZBti5Fd1L3FgtA6oNil8V5EVula8G-FNRTcGH89NP8N0WPndc5VNkdcsWKEVwjx7axmKS2_EL3-STltZk7-eoqHpCejhPo5mJSyyOlZ0JjKDX3Ow1kD3rVjSIZmw-aTKQ","e":"AAEAAQ"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ab5beb5937e94d788ebb0ccbdc4859aa","kty":"RSA-HSM","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"pebUyWT5U8L33ZSuWtyPAcoHvB8gOQySsc1PJVzt03rA5UJrfwtJ4Qr5sHS4phxkUmG3gb4FzUHwztAPRaTdwquqM7b9Qk8OPP1BRZeZrrM78iATXd0gM_xzcFs6GE58WFc-ZFVTC9PDVb9ktwDwwf5nSLGsPlqW0dPZEaOkFwv6w9RBqyJtdvGw7zQGeKm9oRG1eQXNSenhJfiYLyDM1Ve6eiSYEBwiG3KAr7QxBIoRxIqecutM2e7MwLWedoPr0BwMUnI6x9k78ytHv9YaHkRU0cLN34o4t4sghoRGape1sg-SR8NW298U5LyukSRX68zAn89d1LU2X-gBSUA9Yw","e":"AAEAAQ"},"attributes":{"enabled":true,"created":1562703532,"updated":1562703532,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -80,7 +133,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -105,7 +158,7 @@ interactions: code: 200 message: OK - request: - body: '{"kty": "EC", "crv": "P-256"}' + body: '{"crv": "P-256", "kty": "EC"}' headers: Accept: - application/json @@ -123,7 +176,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"B1EkphOo5ST-obsRix4ca85le8s64tgpO9Kc6aMulTo","y":"vhMRFQOf3Bf2KE8ppJGKYDTWKtKlUbBB-ANqxcz7Ck4"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"uiR3274tb0zkEvMPr_sxHsmHhutBdxftwkqFiamW1-w","y":"7xk9VqY3UcNGM3V5gnmtHb3vq_q1Q36Sp4s1zA7lDKM"},"attributes":{"enabled":true,"created":1562703532,"updated":1562703532,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -132,7 +185,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -171,7 +224,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"B1EkphOo5ST-obsRix4ca85le8s64tgpO9Kc6aMulTo","y":"vhMRFQOf3Bf2KE8ppJGKYDTWKtKlUbBB-ANqxcz7Ck4"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"uiR3274tb0zkEvMPr_sxHsmHhutBdxftwkqFiamW1-w","y":"7xk9VqY3UcNGM3V5gnmtHb3vq_q1Q36Sp4s1zA7lDKM"},"attributes":{"enabled":true,"created":1562703532,"updated":1562703532,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -180,7 +233,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -216,10 +269,10 @@ interactions: User-Agent: - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 method: GET - uri: https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97?api-version=7.0 + uri: https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"B1EkphOo5ST-obsRix4ca85le8s64tgpO9Kc6aMulTo","y":"vhMRFQOf3Bf2KE8ppJGKYDTWKtKlUbBB-ANqxcz7Ck4"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"uiR3274tb0zkEvMPr_sxHsmHhutBdxftwkqFiamW1-w","y":"7xk9VqY3UcNGM3V5gnmtHb3vq_q1Q36Sp4s1zA7lDKM"},"attributes":{"enabled":true,"created":1562703532,"updated":1562703532,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -228,7 +281,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -253,7 +306,7 @@ interactions: code: 200 message: OK - request: - body: '{"tags": {"foo": "updated tag"}, "attributes": {"exp": 2524723200}}' + body: '{"attributes": {"exp": 2524723200}, "tags": {"foo": "updated tag"}}' headers: Accept: - application/json @@ -271,7 +324,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name/?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"B1EkphOo5ST-obsRix4ca85le8s64tgpO9Kc6aMulTo","y":"vhMRFQOf3Bf2KE8ppJGKYDTWKtKlUbBB-ANqxcz7Ck4"},"attributes":{"enabled":true,"exp":2524723200,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: '{"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"uiR3274tb0zkEvMPr_sxHsmHhutBdxftwkqFiamW1-w","y":"7xk9VqY3UcNGM3V5gnmtHb3vq_q1Q36Sp4s1zA7lDKM"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703532,"updated":1562703533,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -281,7 +334,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:52 GMT expires: - '-1' pragma: @@ -322,7 +375,7 @@ interactions: uri: https://vault9627173e.vault.azure.net/keys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vault9627173e.vault.azure.net/deletedkeys/key-name","deletedDate":1562686804,"scheduledPurgeDate":1570462804,"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/ae688b4595fd4c28be53181dabc31f97","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"B1EkphOo5ST-obsRix4ca85le8s64tgpO9Kc6aMulTo","y":"vhMRFQOf3Bf2KE8ppJGKYDTWKtKlUbBB-ANqxcz7Ck4"},"attributes":{"enabled":true,"exp":2524723200,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated + string: '{"recoveryId":"https://vault9627173e.vault.azure.net/deletedkeys/key-name","deletedDate":1562703533,"scheduledPurgeDate":1570479533,"key":{"kid":"https://vault9627173e.vault.azure.net/keys/key-name/54916f0a705d437a9a12fbc42040e0f2","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"uiR3274tb0zkEvMPr_sxHsmHhutBdxftwkqFiamW1-w","y":"7xk9VqY3UcNGM3V5gnmtHb3vq_q1Q36Sp4s1zA7lDKM"},"attributes":{"enabled":true,"exp":2524723200,"created":1562703532,"updated":1562703533,"recoveryLevel":"Recoverable+Purgeable"},"tags":{"foo":"updated tag"}}' headers: cache-control: @@ -332,7 +385,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:53 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_list_operations.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_list_operations.yaml index 1d6847424a07..6c925adfa57b 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_list_operations.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_key_list_operations.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault96d6174c.vault.azure.net/keys/key0/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:18:31 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "EC"}' headers: @@ -18,7 +71,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key0/7d0f0245a031434a860fb70cd4bc4b6a","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"iVjh4__jrsm1RIAZos5AWAli7Mcl7Vf5YNKO4vCgmoo","y":"8hR2ieaipKzz-0EGwRHZMxnedsuB1qARh_wkPY1mOJU"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key0/8bd25d6c7e4d4c95b249df2ad1a06f03","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"kt3oH8H10CKe2_VPcIxBJ4PQt6O1eCkmCrtyHmdmF6Y","y":"Oje7K1kKsEoeFQn7WZgJ0pZ5n325HykdIeOXoOgVes8"},"attributes":{"enabled":true,"created":1562703511,"updated":1562703511,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:02 GMT + - Tue, 09 Jul 2019 20:18:31 GMT expires: - '-1' pragma: @@ -70,7 +123,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key1/42cde1cddc9b4544860ac5fa1b39a2de","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"lHbgaiovCBVixkG9cGaZ6W01Fek9je-9iXoe3tsdX0U","y":"46vy0oVybZipy4ZV8c56eQ5O64hM-JKsoCccSKYBgYE"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key1/986a1037ce7845eb9b3927fc07576f12","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"Ulzv_dmHaTAW68YM9dNRAAtYHSxfIWG1pJsl_ZVknvo","y":"ywMBREwV1gC0OBCHul6Mm-sbwku3DjQA1yK7CpQyFBk"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -79,7 +132,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:02 GMT + - Tue, 09 Jul 2019 20:18:31 GMT expires: - '-1' pragma: @@ -122,7 +175,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key2/1e8219a170bc4764bf027d34021f67b9","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"RyO_eCE7tkZYl3vWMryTdORMU9MF-n0MBKJ-532Cgto","y":"xe4qne2b6rbowg18AXUaqyDBqvRB08yKTc7bUoIBReY"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key2/a0501fcbc568481b829637c6a92e4abb","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"--BpzoUqMk2FLfcY1VnrLKSzW4GtjfBu_cVUFATu7mY","y":"BU_Q-pS8w3hH9elSSPe0jKzKj4Ey03I3GdgGy_tS7mo"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -131,7 +184,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:02 GMT + - Tue, 09 Jul 2019 20:18:31 GMT expires: - '-1' pragma: @@ -174,7 +227,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key3/8397c642907d48089d58aa52e340d70d","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"mnl1Vu9GGHr-Xw5zYgqYKQxZyj-HECCGTcdRsP13ozU","y":"tmi2PxJbLDrmth5FlqBUzQA1-j2GrQIh9oCTUU2si-g"},"attributes":{"enabled":true,"created":1562686803,"updated":1562686803,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key3/b62f8f82b76b47549153d116b383815f","kty":"EC","key_ops":["sign","verify"],"crv":"P-256","x":"VQqpb7RotE1iQu9i-7iRZGg8GOpxVQQB6tx_7tMPMZU","y":"SRqLIoGAGpKgrXYP8JwBPSEdW50xNPXpMOeh4YkpvgI"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -183,7 +236,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:32 GMT expires: - '-1' pragma: @@ -226,7 +279,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key0/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key0/00cb084563054c77bd1424dc38abde1b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nZu_SA1KVQJGhOEH9ob_WXzg0mXehAufqC1k5NPJnhvv4Bw_CDsvXmwzF8FG5iRFFzeOBsC3lbPP1IP6ZQOiHXdwdaRtAPhPlK1fRnwVq5X1EHNzyLw2k3PXjkYsgvPf7Jfnh1yEvWgq1vlRBJubBehIo1aap3bkQluBRNslOUUAB1ONxCWtuA5bz4OqteaiNBAzbERi7Ra2AjZFKJmvcwJ1a9_hzBwgNrjrOZpTah6lyutEZywuCDC8b7eP8cuu0BTzGOoc9NCB1wFP5bncBg3db2uFCftADqVeG8TB9KY1i6G6_mXvKwO-T0WymacC7_BxsfQe-CWmAsh5bxWQmw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key0/6b36301002db4081bfc37dc15a3cf31b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"9rz853RQmmMerwKCfQpSONzDOwC9mniosBB0IWOQldmNJL53cj-yYVaXIaXyblWBA3uDkwWFLFvtz8_mxDowBxMamsJ46_njsKswVOHCgvxwilwc22EwEPyxaQimvRipMtU7mHMEyETOhvMKNhifTa3kv7YKKNGrIO2Psa2MvH7mxdYmjFhbPgdXwF_b7cINa2UGrf0fI_aXryJjY7E1L6J2GDAXiBGaVh59FMRU1DQQkRBBDVIzuGep6QLhpi9Jk4fUKYpFwL6ees5KRmg_Ztgz8E15yWkSRmsoEcNvrL6Enb05blHGNc6uCUYrRcwki1VV_mGW-DWEOE0zHaQysw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -235,7 +288,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:32 GMT expires: - '-1' pragma: @@ -278,7 +331,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key1/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key1/cf6653d936514a71b94f5977548bfc4b","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"rqWKcZbeNtueEYPwftJ-V_RCjisNgTTbkB3giF2IDQv9kI-FAWlm5CiipyUe929i-EcRn4KAv7r4_m_lRLoe2xTrVcX8RAS1jMVBGmhnPSaiKgCeNM3zXeBMhmQqi0UV1jmX9HEb_x2LDfmlXTptk0rG2fC8cjOAo8bikPq8lS9z2rF5iEfZjAhm4kmBF1nAy1LDR3EDCI0ZPE29kS2rXjRLS3OGl8YmAnUg43ApbURxmZwCi7MtCUNbPYTULHcUNQe34jkGiQaGBd4Ghbl0t5Ai8XZ6wHHaYsC2IGssZja0-dpSDF-m-KfASrbxWvNDdQsrkvYsw2Nq49LFN40aVw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key1/a298f2b63271489f807d41d71a2e10c4","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wWuHzO89M8QbAl7BsuKWydhq_xj0RkrAsUjJTtRiLJs6CGl4jkvI9jBJb25Wis0anulK7cQKLv9K7MwtZtpLhnbUya-Qj_D9d9BSOCyjm_DFH9v6tNqKuexqKDu2aUrulDxXqUng4jW_4Is7P2g1uhkdoMJ0pzkToEIi0TUETmfUT7SBOgwr3zwPKcLGeO8H5jwT7AS7lVaz6MKFn07vVnE6sVq6Jxi9jyB6IHO7iwRB6w3cGVPexJGft5m3bt9Kuf47WHyOzVHqmwMMv5W2r8Wb5zVv6cVnb08o44OqGKOGXk0vqqiM2QAB__vM5MbVQMK2TcuJiotjiSHrNJZ8pw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -287,7 +340,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:32 GMT expires: - '-1' pragma: @@ -330,7 +383,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key2/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key2/4ec3e2ef35f04734933f35c92967a0c9","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"ivJ8CrUCYRPP89FCzmIVj9dFw0f3_JniYKcfO0j3Cq4d-fboB_yKIqvK9q45ckzcnpC_YY45UdYSiYItHs3r72Ya_Kk__hUjIe0PkngYFQmLCIzFGJ8T5QpTCOXwwhBjvjA3-D4BwZ4q8NRi93HA0lXOrrieQMo5nzym3uwsx9Dc3wwE0E_qAnG8-XA_2kCM62yCFNtvqndyljSZyYDp-CXRbZ8K-c5b01ZH7KeVqq56zuHrTsGUkRzsBv8seAsQyYIAGRwlbf4mWwWW_tdNXO-P6GYz3tSNfJxz_J4TGhBsBjhBDeMIoTh-rMO_qiKmEhkEyvgCG-3q1LBxF0EBmQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key2/022d0289f15b460c830fcd30ddfdafca","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"z56wacSTQoxXCtyq_6uTAUvh-UESgYVJy2RbWQ5DMl1ZJDeIHTublcGy8e_kBOUgkiCFhObP29e8DaTEhuc-vJ62XX2EuDzvmMp2tUido83IIeXatP1gb4TQpXAVBviFXdPQNbce1sDZ5XpA4Zi8HCV1QqVyAJLKTJB6hOpwTW_HXG2qpYQVqOhMXv-39ZkWnVWSWLfz9kKDvfbXCeBDiLz7iVDVdB8ELExAi6vz4xjCRZPExQCCvXXu22OsbehfoW4FLb_NhcTjmTQQfCQ-3G_eskXYl8envgVsi5dxMqklnb0aHM3PTadXrY1wcnjLhYL1vgC7LKS7dc8c5rX29Q","e":"AQAB"},"attributes":{"enabled":true,"created":1562703513,"updated":1562703513,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -339,7 +392,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:33 GMT expires: - '-1' pragma: @@ -382,7 +435,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys/key3/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key3/a5e49235e425436c889537eb5da653bf","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wWP0v6q3H2RHCLiOTu1wsFcE0G5LOJuB4Xo1IEwVHCpKpdZf4GL__Op9Y1wx071jr5ePNniduejfP1CENFxP9C8eKfbjjwBdHwxMiHUsLpfRs4Fe3uAVGp3ZN_YCzmOXGNo7hCWS5E5VZ_hF77bK6B72geFdXyvvXpif3EfsIOcj1ghxN-ZHmw7FTDVqEGp4VhS2dfnB7Z43-sAjvok3LCnhaxqyEoulER79chH3eNWbm9sDRzC1MUY6oO260dx_BUsJW1eLXrt4dBiqNJgC0HkJ53rpDU3ZnPdNS1n8qC3Q22LmnoGkSGHfZ07Kgvq9n4GWLe2IiRWmYVhIIv51Lw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vault96d6174c.vault.azure.net/keys/key3/f9aabe1e98594882908b42b110e3bc82","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tazNLMksNQi1DwDd3CF2qpnUteGfDXKoraalUO1qmpnVVta8m0z3Ekl9QQocQNFoJlIAnG0PHRrnKRdLt5QMiacE4VO61G-KqP7l-2L6CbC1fEywIqE_FNC7U1neWDuSpel5s1v9HSQp616yUz4zGaBMdwzPykyp4bo2MPcRXkrpZjOBUaX2Z9MGW58Q5ic3Aj3l3MrNY_uOj8JdisNSShn0tnzez5QOV1rdEOcYzmzrNCr00xqjY1qE70uhKSFfdIuxKEsRU05dCuJTbAyLKj_vhsnnnyKQEN1pXcQCO2XaH_ZlCsXdvwWbH5Ptldyn2AHJQ2FT9iLGNOmgr820jw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703513,"updated":1562703513,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -391,7 +444,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:33 GMT expires: - '-1' pragma: @@ -430,7 +483,7 @@ interactions: uri: https://vault96d6174c.vault.azure.net/keys?api-version=7.0 response: body: - string: '{"value":[{"kid":"https://vault96d6174c.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' + string: '{"value":[{"kid":"https://vault96d6174c.vault.azure.net/keys/key0","attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key1","attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key2","attributes":{"enabled":true,"created":1562703513,"updated":1562703513,"recoveryLevel":"Recoverable+Purgeable"}},{"kid":"https://vault96d6174c.vault.azure.net/keys/key3","attributes":{"enabled":true,"created":1562703513,"updated":1562703513,"recoveryLevel":"Recoverable+Purgeable"}}],"nextLink":null}' headers: cache-control: - no-cache @@ -439,7 +492,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:33 GMT expires: - '-1' pragma: @@ -487,7 +540,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:33 GMT expires: - '-1' pragma: @@ -535,7 +588,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:33 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_backup_restore.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_backup_restore.yaml index 8fbf4a3838cb..16f0745fa807 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_backup_restore.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_backup_restore.yaml @@ -1,4 +1,57 @@ interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json; charset=utf-8 + User-Agent: + - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 + method: POST + uri: https://vault96041739.vault.azure.net/keys/test-key/create?api-version=7.0 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Tue, 09 Jul 2019 20:18:31 GMT + expires: + - '-1' + pragma: + - no-cache + server: + - Microsoft-IIS/10.0 + strict-transport-security: + - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" + x-aspnet-version: + - 4.0.30319 + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - addr=131.107.160.58;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.1.0.872 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized - request: body: '{"kty": "RSA"}' headers: @@ -18,7 +71,7 @@ interactions: uri: https://vault96041739.vault.azure.net/keys/test-key/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/658aa7c54d564c7aa20e529f12a9894a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nRkoF6WTkMhvxLuxkozbNkz4uqgpJyCWMU8DkIme7oop9qyVQy1AejXbY0_OGjn4RpN-TRhJ7NxoFBb3Znq0FouSjp_a9GrUwVIfNF5kJ9yJSWnn_SpIB1ZwFxph8O3mq5YW84L6WQ3JZvjhF-IqDYmfEzjwlVrnBjIe-Mk6kT0BcXvaJb-g_EkbcmSrm09SKul_hQB7Ga_yPT-gj6Yupb1swEnDhEWcchTcAnkEwxiQSVbuFxE0nIv0U5T5x41n4NEGeuezOUjKA4hrx5ghROBK968DFAqjEpdfrYEBOgqn3urN5GnSnh6S3LmMRKMnGdSOhAhCY4HUC9XqsXibKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/7089b08b10a64ec68db241e20e2109d0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qbGcivUVxthKiCxcjxOGdsiItrjjTm0_-o0gkZmRSDlEY4ixbjJYBZMwZLxLm7_ZzoGTVTdON-_6bLu-ZwFqAAe2Nc_HDLkL3FTn1YkjTSA5I6FTXI-CC79m3mPLbrVNkHRUkN7QPpl9Qak8RyRl4TA5XtOp_ztKm5Lfuc1eJsosaRdqaMe2UlEbu0XCdYDoP1UoNRAhrUZwli-GHH6yWQxq0SBtnfUsXH0CkgprrnpDYM3dxUkjJJ6IRpW4acRXrFDg-ccCBQDRLty6eSRehugfZyTGciaT4fBcj245OjytQZHxz2ZGixRxN0YsvjoZXqquXKqfkKEcknMDNapyFw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -27,7 +80,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:31 GMT expires: - '-1' pragma: @@ -68,7 +121,7 @@ interactions: uri: https://vault96041739.vault.azure.net/keys/test-key/backup?api-version=7.0 response: body: - string: '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLkUzYWh6U2FiTV9JQ19sUEhKUlU0NUptUm1PSklydkJJRVFmR1BCc0N1dlZHQ1hpQnJmZGd2ZTNSSjJCQXBvMWR6VjRQUlFpS1BRRDdIUElaWlRybHlESkJfWTVLaTM4Mm5OS3lrM3pwSU9hWXRDendXUnJmaHFnUUJUMEVxN0FsSXVUOHZUQm1VZ3pjMl8wZDlkdlBOakwzOTFSVnlKUDhjMGRCWTd0TmQ0TVJheXN2a0NfNElTbzZIRElmX2hzeG9KbGVrOGJJWExjUDZPekFkM09CeS1BQ0ltYkQ3N1NQRW0xbUtBd3I5UjVmM3lTOXlKVFFoZFExb3dZd296QkVHS3FhS0RNSzZENWRwRlRuWGJTX3R6cWlHVlJ3SXVRaVZSTlZ6Z21hZ3ZwY1JpTnJORW5vNUQ4enlBaGw1SFoxUVJ3Wl9nZUcwcFBTeU9SMkhTSVFpZy4tZmo2eXIyaUFwMWhoR1B3SU5MeUdnLkNXdE1UMVJDUzhDaVNuZWFidk8xQVRLdFF0RmFRZTZPSGZmd2g1emhFOTlCZkNtekM2RGk1Y0huMFpWS0JOc3hHY3A2ZGx3QkpZZmZUMHZMNi1QN2lUY0xsNzdwVHhfQ3pySm5RYmpCU21VTzBUQUtaMUVFMEFvUnpWY0ZaX25TODRTUE8zQ3N2RGFxYmNscGREdmE1cGNod21ORU9KZnUzM3lPOUhNejM0MXlLajZPaUNNYlhFaTdjSkZlQlJjUW1LWUpNQ3hfS0daOVFJdERsSzFLYl9HWWVFNmVZUjg2b0JvTlVBdzFnUzdSVDZiV09jNFF6MGZzSllVMUJTZ3NteWJRWFJWclhZXzN2TjdlaHNrM2NfaWxRTW05RU96UklGYjdTNFhsODJKVmFHLVlPU1ZSTmhuU0JWVy1UTTc1OTJkY1dVYjFqcnBMVE5DbHhSS09pR1NqQmk0UmFjZC1EanBySG5QT09FYTAxRWtYS3ZQcGlmZl9UUlN4ejJaa0pEdklWR1MyUTBOUmJoSDlkbi1ab2hMeHhpZUplRERubjFCZGhjUXlPQW5zYTRZbjNHTF8xSVg0ZXR6XzdOempNYWVIYU9JUXlsbTJkckgtVm5FUmdaZERLeDViRkxrOW1KX1htRU1LMkVFb0s1WW5VbUE5SnVVMU5PMDJxOHFDYTBxSnVmMWJoRFladWhVY1FmUFp0VVYxc3ZlSTY4c2NuajRUWFIwYzhIMlRPaU0zcWkzOXptTjNFMGVyczZ3aTBUYkw5Y2dUZVBJNEtoVkpxendKRUU2V09JQjZVTzZrSDFwdXgyRkVzeTJrLXVRQ3B5WUFFQUo3N2h3ZVdOZ2RvQUFwWUVZUHVOUmk3bVppejBVaEY1NDNUNEthVVVuazd0cU4zVGROQndVcENQYWF4c09OckVFUVMtWnZoMDlBcTJjZVJ6MkxIWjBud1dZc0FRZ1RyY3RTYWxUYkFGcENZM1BzT2pyVHBvdDhxcG90WlVZR0JuYkVUVDNKVTVxT0ZybU5Zb2ZUY01pcWdpR1hoR21kQVBZRm9GTUFKV3N6VTM4NzJySXppOFU2djVXYm1Fam1HZkxGLUVKWmFsVUIya2lOaE9nRmJGZ0IwVzdJR29qQUhXV2NaUTNtd0gwa0lEd0dZckc4RnJwTXU3NkFGT2xrZ1hadWdWRlVjdGk2eVpkbzRUOGx0V0JIdFh2b1BTR2I2bkFqT0xKWk1GaC1wejhTYmtPZUdsb2RHQXJSWmktTlVsYzFodkpwc2s4NXp4Zy1QRFpuMll2eGtYQW5ZN3NDOFRMaGR1a1dNWV8wRDNwMXFCZDE4T0ZkbzBKUGhWb0ptaGNSZFAyWXkzV21ZcHdUOWJsdWxTTGNzTklhbldHWDVHQjI3RXZMVjZHa1V0ZWtiZGNyUHZRZnRrY3FNNDJqTk03akNUQzhlOUZvSFdIakk2N29yNzlHMlo0bF82VG0xWG1sa1M0T1BvOElQcGZyV3BkSTRmd3FqeDVRXzUxRDNSMjY3N3cyeF91NnhNMVQ2TmlYbDJESXI4ZUx5QXNMeW9TZGhVV21CektvcUNhdHdOQ3V2Y2pfVlczTHMwMzRMV2Z3VGpPdGw3WHUweWxneWtvQy0wNm5RUzhzXzZlcXk5STRtbC1kNWhMbGpZSFlUdEFGZ0d6Z2RFMFFkOC1tbVF2TkVuaWRsWjczWktpV3Nrek1mRm9kQnRIWTZVWUZRQTRBVE1qZTRhdGdIa3Z5OWZpSHhYZHcwSGItcXVxVzI5X1RSVWJIYnc5aWhZVkNpRHlPUVJfbXBOZm54Z3hNbjdkZFotNnZXNlNNTWQ2Nm16TE9sWElIZjFOY3V4SXQ0eXJseGVfV1JnbFVWSll1WVRHOEpsVEE0aWR6bzN2ejJzR3lVcWV3enJ3S1lHZUV2aDlTalFvM0pPaVpoR2pUUTNUa3lNYVdGaERKWkU4UHdEUjdpclJXTXhUZ0lYR1VPd2haTE4xei15RTN4WXBUNXdQY2tLSnRfVnNXckFTZGs4U096VmRXV2ViQW0yYU9HWmplanNXbG5iNWI4SVkzanJCeF9GREd1X0M3Y3Ribno5WVZDalRxbGc3QlA4MXg2eGdXb253WF9NendSek5Ua2NhdExqYWdpSDc5UFNBdTg1SlJGbzBZd2RRai10N0U3b0tSVjZyUXRhNktia2JrcnREWVdBZ0ZoUHpSa0NPSWt5bHV1T1M0TllqYXh2ZWVFS3V6T0I2SUhNeVJnRWVrbzRNVGJmOWhTa3VHdnNTMnJtOU9JNDdFZ0lZQXg4MFBPcFpkcTJDTF9FdGJlal9YdjFOeUg1eEd2Z00tUkk2WmNTWWZ5Vld2ZUIydUVYeWpRM0ktWEwtemZSMGc4ZDVoeGFSRzV2dWdCN2dKRnJVWHZGZW1jWk1NUU80MG1OdWdlOEpBTHJXMjJUSlE1bm5PZWVVaUpHb05NRWY1OWRPN1VpeGIzSXBpR3FnVUd3Z2JCRXJaV25DQWtDUVFOUENESHlCcjVTZEs5Nlh4Rm5LVVV1dTNfZ2UtQXVyTFVxTjYwdXlaSGpheEM4dzJQV0FZOFpBMkw1ZjhIXzVILVcyeTNMRkcwTDQzOFFBcF9mZ0UwYlhZZGJiZTBLV3ZCUUdhWmk2U0JCQzY3UUR6NkcySVV5d1hfTVZWcXlod1ltMFY0T2V5TVJOdXBTVWlsM3NUYzEzZW52czJHcnVYRDcwOF80MjBUdGJ3U05DUVB6VXFWUnJOYjFER0VrckJlcmV6LVBSc0RuQk5IbWFheXZNRnJfcl9ZNHREV0xZVTZ6M3U1X3dCS0sxd3U1THdCbkdMR3FmbDhPY1RyWGhVSHVEYU15bGdTelZBVHpoTE00NDNfWDk3TU9wR1EtbC1CTmx6NXZkYms4RllLUl9lblRoeWM1Mno0cnpEVEhjTi1ST2hldGpOZjN2TE5ZcWpzRmpaQ25OanFUc1hsQnFDWXZTSElXaUs3dVJEQ0dlbmY3dVBkNUgyLVNpQ1hCbUFrQURadnlBNExCUEJiOU9SM2dzQ0dRRUJBYXlaTmk1ZDZvVjZvT3REMXlmRW5MZ2hhUXNOaG9BNnA3MVdJTjhEakpUeGFsWHo4bGN4a3lqM0syU0FBTUpzblpldGdYakdlbk8zWjVZbzZHSmUyTUZxU3dkX1k5RGRqc09pMndzRlVKMjhubko0Wnh3N09ZaFkwa2FmV3hjWU10WjlyMGVjVTBIZDdiZXNjbWlHalJIbFVSV1QwS0czTDUweGFrU2c2RzAwVVdxR21jcUxuRllSdmlpendDbl9yWWN5UzNYSF82Wm4yTk1VQ1dFX295RkNEbFYwLUJ3clpGLVhKdVRoRmluRF9MUXpLeWNxcWV1MlZmN0ZVV3paWV9SdUZBWXcxb2ZSZzRtZkdIa2VldllKN3NQX2ZnSFBMTFZVSHZaX2djM25BOU83d0h0LWdiNElHYWE3blRSb3ZkanFZZzRjZWdnNFdBc1FVWjBiZXRHLWUtWTAya2RYd1BGRnZHbVYya3M3ckE2U1VmNHBDX3E2cVZWbW5xRFpVQWNBaUMzUWFKalduSUluUmc0ZzZxSW9yaDFLWFVoVDJDNWFEQlpQc2x6b21JNUZWMkE5bzBhVDB4ZndNODl0UDdrQngxRlYtTmdERzN1a3M0eTFRVTkyM2pNZnotU1J1a2h0R2lYVG9Oak1pMmJyVV9sdHE1M1hTVEptTU1HakNjUXQ3bmhiM1YtLS1NRWhXMy14RVk4SldDbXlMYTFaajZ2MG1ORGpNOFB5eWxoaFh4NzhIM3RPcjdtWnM4Ni1scmpfNG9mYmNuWmo2bFhqWGhBVVBIZk1ZRDN6M1FRWHRIREFvUUFDVndNY2g5V0JqYTIyZGdiSzhmelRTZ0R3SW8xNTJtR1BUMW03dmZDbXkwdHNvY2Q3c2ZMYlUwOWdqWF9fTEtQckxLNTVFV3VBV2cwTV9BU3ZBdUZqdEoxV1J6MlV2eWM0MTdNekpxRUdfYndiWlQzRVZkYjdKdkh0QThxVEQ3X0d0Qk9lc0FkRmZsMFVLSTA1aUNPLWU0cjJvdE1PQjFYMlZNUVRKQ0drVGxjd3ZudndfUkMwT3B3YTZ1SjQ5aUZaQ0IxZzZhQUhqUS1lWWtndGs3NnpBRXBkR25uWVQ0Q1NmRW1ZYkprZmg2M3N2UllreXNWaGdsZlhZSUFjU0ZDUF9vZFczMU1vblU1VUd1Vkc5TGI1Z2d4MEx1ZUlXMk1wcDNhTEp3ZkoyQnpPcndIRWhhaGZjaUpTWkV1LUU2YlJhNnRERkxFRFRiNG1TVC1yRTA3R1JlN29LTjB5X2ktZ2p1Wmx4T0xsTnFKTVpTUmFndEV3N0loamoydjJuMmNQeUZEYkhxYVRVOVNtUU1sZ0RFelFrNHNpLUxyTE4zaUkxb1FDOWVMUGZGWGhOQktWVFk4dGNwLURMR1Z2aXY4Y0ZMNmhXTlNKQ2c2c2ZYb2dDUlBLVmw5SHVGejcwczk4bmdUdE0xV21aeW9xQ0FEdXNMU0xGcEY5SXRnaUZ3T3BCWng4UWpNalJNZmI1eW9IUmVHN1JmVFBjZUl1VV9jcGdQajNRcVBvT3ROb1YxbHJ4VF9KTFBPekpVSk5nNzhCZFQ1Sk1wLUtzbkNsZTFOZ01BQ01CandxMTQ3aUtOY3YwMUhGb0xPT3ppMlhTTDJqODdXVjFuQ2tvc0J6TnNEM3lCSkJ1bWZublRIQ2dULTdmZnJRekFsLUxmb18tMlRPTWdOTm1PT2NmVHlxUWNXRXBWNTVOMGkxc0NpOVBlaEN0SG5lUnp5Wm85bWxYUnZYREJ4R0VBSER1VFVZZ0dEMGJUNHBrMk5NY1M1dmlEQnhOWTFEZEprbXlEb1J6Q1VQMm5vX2VxRnFDX256NXpHbGJ1TGlBajBIb09hejVRcHlvcklQZXhqdHZXOW9oenI1c1lFbWFjRFIyMWNPQmpLMjZvQzR3M19UWUJtLTVxUDVzdU1nZExqSVNlY0pwLVRYaVRLYUtPSjlIQndoSmtGQk1vQjkyQVNid2dSTTlvTDVhRkFNLUJrYVE4cEd6ZHMxUzF5Tno1TnFwdTVoSTgycGhBaVFvT2tFOXRnU1MxSHdFd0VYRUFkUWU0QmU5ZEw4RGotYkdXYXlWSjlwN0tMdXVmTzRzWV9JSTA3MXVrWG9FdmFIQU41cUdyeV9iTW1FWXlfQkxKR2tyd3J2ZVlOV2pQV2ZpeDNKSjd6V3FYS3ZHWHA2RFVGTldJYnRraFN6QzB6eHNXUXlSZm0tbGR1Nmc5cHhIdjVta3pOeFpNT1Y5RS1KUV81M3lVZ2tfLW52RUxDNWFHc0dleEVaWHNJQkdnWXZmaGpfdml1dURkdFhsQ3lFTi0wSXVyaDROaTBNXzVtQS1McFFiME1qSXBOZUVlaHI1OFFuSVFfQjhibktILWliWFdRcUo4TEVDUXhUbzdGVzBBVjFBWWtTaU1yazZhVHFwQ3Fvc3IzQXlnelZYWFp4eHJhYTdhTEdVZm8xcHQwbEwwakhoSm5Qd0hOSnFCMG9SUEh3VzhHSjR5dnM5ekhuVFVpbnFvQXNzZE5FZXRKa2dTMHNxX1R3NU9HekFJUkRvcGJUdjlhb2UyYTROWlZ1SDBacjhLYW40LWRkQ3FVdWJzTlV0VURqaUlBOEtWODZCTm5SRXBQbUJIS3pPcUg3Tk9UYUFoRzM3TEJlZ1V1SGNEb05YeTlRc0t3TnZjSEptdEN0Q0dJZHFDMS1QOWJLbk16LVV3LUFWY3c5N0tFODdvai15Y0sxWEI3eWtYeU81QjlFcUJPczk1cXZzaVhKWFJtUkgxSDVHdFNkc1hpNmh1NGpkemRteE1kTXZURUNYQ3pPZk5tQzVJNTNVcUltX2tsaEdGV0RYZzI1S05wR054ckpXTFpGV19nZjI1RldOS0daNTZUeVBRNXFhLWVsQzN6NXV1WW5WdUU0RXVRcXNFbE1ILUV0eEFHN3VwM3BsTzBoZW9qaFdzTVc4NlNucG1Xa0I3bklwcllUUzRHVmhmWnhoRHBRUmtSbUdfRG5FNnBDMzc2Q2xtbDF0Q1VmbHlqbnQwMlJxc3FZMmxTSkd5alZ1SkduMVlybTFBaEVGc09GLUJ1UmRvRHEwZFJscVQzREV0RVV1ekxQZHdGSGhhdWh0N3JxeTdON0tfY3hmRDBfS3dmYUt2Yk55ZXBHcEpTRVp4bWs1TDlpSHJHeDdCaFRDTWw5TG5FVWxVSVc1REJYOXVWWnl3Rk55NWRnTW1uU1VFUW5GVEE1Nk5SWFAwajJGbjc1VGdGdGtldXNuREdRT2hjbWZwbWkwSVhfYklrdzRUaTVIV1pPb0NPMTdhQzFYRjktT3pQYV9pdmlzemE4bmVLeFNueV84SFdrSDNRZ01EV1BnQVZlTUVmODNILUhfZXJaQk8yQmNwdWprYWtlVzdlVEJKekU4dVNGbEphZ1dqX3ZjdzVMeGdyUldZc0YyeHltX2lKZVlVNUF0bC0tcFVBNDhtV2dEQVcyZWswVVVSMl94YlYtc1dEX1RkVEVqeTFDM3M1UUt3WElubFRFamNLX3VmbnJodVNfWXRULXRudDE3R05NZUdnQTRxR2VVbGhjdkEwczdDcnVkRzJ3c2VMWnVJV0FhNVJBY3J0WnZscEhGMGs0VzJSR094TVQ2U3BIRVFDcXFEa05Ma0p2dWNxRmgyN09QXzVPeVlnaG5DTEZLRVNKSVg3bEpzSFhmUG9WVklYelA1NXhTeVBjWmRyVlVJdzBQZ3lJcGZHSHU2RW9iWVVKOVdCVE55UWlhbFpTVENPNlBhaHZmUDhSWnVoNEUzcHR5N0xpSXE3RWFEUnIwTGlhdjU2UXIzcm1ENGVMZEJyR21oc2JBRUtOU3JjNDBZQndvWk96T3htOHJzeHdBMjJDZGVFaEtoTk1RN09PMllWSHp3bm1kSEFvVXdNQ09FQVN4U1pjLUVudUI3UThWVmx5SnBISy1VZGdHTmxmRW50Ml9ndG04VjM5b21abTJ0VE1vTE12SFJadGRzdHdVWFVaaGFESUFpTDFoV2luMDRFRUp5TlBsOUhzTFNGR0hCU1VVMXREcTJuTUswNHQ4bXdXVDRlZ3RNcTZ4SGpvU3NzRm9tVHBnMjFXSkt5RWhHZThaVi1PSE1QeHhFRmNBWWVTNXFzR2tibXFMVXgzVmFqblk4WVdocTZkcnZHeEliYWxod2xFcXVDSXJPampQT1V4c2ZwVzcxQkR1b2ktLUczaTNENUdsR1RiLXJnNFduWG5wSHRvbDloaXl3Q254eEk1b0lNMmVYSXlyMUZfbHRPU18wRl8yZVNYbi1lZVFENHloaUJqM1VxVDJlT1NVUi1aWW9EQmY3VWlQTU14eXhsdzM2Y0d5M0lLcUtvRzJieklLMkw1OV9UQWlsLXJuWUwyOHBUa2hDV2ZBbE8zTVdWSEFiS1pTWm9LbFBzUldlb3ZQczl5ZEMyVTdSUkl2OE0zaFkzT3RoWVJKNUVhenV3U21IRTl3X2lhOUJwdHVJYk43eE5IQ01VVFNWMWJWekc0SF9FR3VwRUpyaXVvNTN0YWxZX0lRSnFhYXZuQl9QLWEweGE3aDUtY05tSnpaZXNTekVFNm1PUWhzTVNWbTFTVEVFMFA5Sm5RblNiOGp2aVc0Q015Q3VWS3FVNFdvWjN3dWZmUUdDYUg3ZkUxVjZyc2RNek9KbzIwOEVXSDZvTGFXLUJseU10UjhfVWR2TEYzdklTZ004TTlqZTR5LS1udmY5cGJFZFluTzl4SFRDajEtN0xRVW5ZVUYtSk9LYjFLY0ZRMEdpUnVFaVhCNlA1Y2dRSmsyWHRQaWtYQ3VXOHV1QTdGSWdxdmR5eUlmWUF0VW9tWGdXX2NnQXVEbGExSTNSQkJrQkpkT1BGU0NyVnRKSmQtamJSY055aTFfTzdkSnBvTmpxYjZGTjMtOFF4cFAxUG55b0pYS2oxSzFPeUxRaThRY2htUDNCeUNHN3ZuOGVXWkQ2b2owU19vRmZEV010cnJTamhwcnZjMlI5dWxiOF9FOWxWeHh6aGU5cFBDcmxmYzBqN1E1N0ROSWM1aFhaQXZhcHdpc3VGSTNnczlMaGZzRzY1T0VBZFlKUkFiSmxFNUtsMEwxWE9RSVBkUi1ZQ3p2V3FndjAtRWFGOVFlWGpuaFZXRG9GQzBSaHo5NjFjMmx4Mi1VNGY0Nm5vdXJjUmdsTi1XdUlLTFM1S2JsZTJVVUlNUUVCT0l2dVVaZGllMTlFaktqR3RjbTJ5b0VidWIwX3M3U0duZkotb1RIbHpKMDlDMXRuNkN6U1AtUHdIVzZvVnBSTnBKcGdFdTFuaHEwVXY5bzdiRmF4Wmg0a2twTU1IamR6T3VQMnZiTGtWbFFULVR2U3EwVVF2cWxDQmstTHpFVzUxNXUtUWZDNVN3YUhMWnVIMW1FOGhoS3NYcmxsQjhzX1VPQTlkS3VaVGl6TkJQbXlHdFlrVE9pOEFpaXVfS0o5VVJhNWZHLWtiM19BZk9McVpmY0lmbzlTNm4tTlpqWmM3T1NvaW9wTXJjY0pjcjVQRWZDd21LRnlxZVZTM0tlZkRrR3l1bl9MZVBWVVJWQmNnVkE1Q2NUcUxUUkFoVC1iVGRVNTNIdHdJbTctbGcyTnNDdWQzaVg0OExKVXlBNzNZNXFnZFpFcnpzVmRua29YVGJ2MkxDSTNOblI2UXg0d1h4ZENabE1aYVRfaDVtQlZfY2JHXzF6S24xWjRMbDRfNVFDUDZ6cXh4aDZGZmtDWlNCdVltREdNVENrbDRhS0tQRmpQWDJMLTNER09VTk5fbnhMYjdObmc5RWNfUU85N21DRmd0MVJCLWUyMnc3UlFfSFEyaUhPdWN3N2lHR0VIRTdyY2dVSk8yeWk3c1lNSFhNUGY2MWd0aWp5YTJKRm9LYTRZR2J3ZjBMTUF6Wk5MZnFpTEgwOVYxelRncDB0QV8waUFXM25HSTYyNVRnQWNQY250ZzJzRVczVDNpaVYzcnRXZGx1V1RhRzBMd3JlZGdXSElteklUN0c1VmdCUW1Cd0c0MjE4VTRPcmZqX1k4c2VFcllfalRMUmxPckJHQnBvRTZnc29SRnNqYkhrd2tCbHNlRzNmNnBBOExsc3hQMjEtVWNsNDYtQmljQ3JzN3Fkdjc3QVZwMFJHU1lJSVRHNmFOTFJYMlFxZUVsdEFhVGJDbHNraGV3bzBVYjh5QXpnTkI1LVVMa0tqSHdqRnRBLWlWd1pPU2k2REYySlBjLVFUMUctYk1UcVZmSm9Uc2lZakI4eDNKRWhhVC1CQ3E5ZXl1SWplUjVFZlhqWjlxVzJfZUl1LTduZmtvbEZ6dFNOZ1BIOTZqblBzY1ZjY181UWluNEpqeHNkVEZTWWtCak1TdjB0SXdBbFdGZVNJd1otUWNkOVlhOXFJNzlpVlFmUU54VkUzeV9YU3FrckFCNnBnWW1jNVk4VGJWSUdINS1HWUFaZGdxQVZFb1VMdldyZ3ZBNUNscG5fTkFCM01DMEk0Tnk0NXlnbjlGSUZjSUYzN29GczNGdUVsOEpCenVYSUVDR2VQNFI4VEYyb29aZ29jeEFndEY1TzNaTjMySE5aWTYtZWxZZm5SZW5hbS1TaDBldlp4UnhaWFZLT1Uxclc4Q3ZvWFN3VU1KQmhRdVV1TW9CT1o2OXgzcGFNQU5kMnVIM0VnR0x5TFdYTFIzQThiRnFHZC1TeWpiSDlqTmw3SVNVYk9zVnQ1b1ZTdDZtMjluNnlEOXBMQ2JVSGlVc3ZRYmxaUThpQ1JLeEpMYmtqN0dISWx5cmgtQXZ3aFYzakU3Ulp5VU10c0loSm1DNVNuelNHLjE1MkN6c0pmY2daV0F0MGVxcWRjM0E"}' + string: '{"value":"JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLk4zTkxnQUltVllzMTBLb0R6T2ZkcGI2aHRhMFBQNGxQOXh2VWQ5dEJEaUE4X2dPa1hQcnFza1dMTDd6d1psd2FOYlZNbFZpbnVzbmVwZndNUnlQdENmaHZUSjBMWG5PdUh0TmJfcTNpY1F1LUt2UnhfTHZpaGp4bnVCMUtKNW5ac08xR19RbTVPLXEwdkZySGZJNE9ZQjhIY01fR1VaOGx1dlFYWW5aSl9mY3ZaREZmazlWdVRzaW5fQmxCbXN6Q0JrdnA1LUFrMkwzQXFDUml5TUNjNTMwTVFPbjI4eElXTEhUcUdBTHpLM1pRRTNFMTdqVk9iT1ZtN2ZoWWh5Y3JHYjJWZFpQdmkzR2Vmd2xQZlFNal9VUlM3SzNUc1FkSnRUazREdVdsWkZJTThHY1R1NjZ0dDhIblA5LUE0Vjd5RTliQWJnWXlCZG9QUnhzem01TVg3US54dXBZUjhXMGdvNTI1eUlCSVdsUzF3LnJ0MHZkWFFDUlAyYjJ5b2htdk8wdlRjY1pKLWVlQWNlVHF2WVpEd0tqX1R5SE5iV0RQcFQ4ckN1Z3JpcVJ6ekFvY1hNNDZWME1Fc1JFbGNKTnBKNVNPR0pzeEQ0dVM2c194aDlCQjgwUkRxZTRHcllGckpsSlc4Wl9GLTljMGI2dm54aEhvck9vQ0VxS19VU0xTcU1wNE5pS05CMlZ4aGV4My1NSldJS0Q3M1BxaTZQdnU0MVZnUlR1NGlqTmhMeDJYQ3NtRFpxZkVuUGZHeHFQcWdVYTVvRm9uYUh1enNyRV93LVNORlhlWFQ0Skp2SWkwTFNMRDF0d25CTkVpNUo0Qk16TzJzZExJODhWX001SlpBLXp3RmtGRGNHUnJGeXJkOHVwaDhMVmFrMUpKZExvWkxocERmRjRpQWVLWW0wT0ZtdV9zNEFzbWJCR3ZuTnpUS3IxNlpNZlNrcUtuMm5uUHc2aV9JN3FzR1lEX2xEdm5KaGx2cmQweW0zb1RlSEFRR010OFRZRFhqNXZTejUyNExyNC14M0JMaWx3QlI3UF9SQVhfa2d3LTFYbjdGa3pGM0lscm8wMmNsSUxPWDcySjJUT0JRT2ZZVjRyOGxscE5Bakt0SEJ2cFFRMXV2MTRZbkhhN2VKTVlWWkEwV0VnSlg4elhJb1Nwemd4VnNLSTlRU2ZQLWZQVGdMQ3VUNm1iZVBHWWdSczE4dDdjbGhJaTJwQXRtaVYtam5GVXZJMXZwbWJzekg4SDAzYmZxZF85QmpHbzlzRklNVTBwZjR1Rk1aSmpfUnBldU00MjBmak91clpNY0xFV3lYQm0tU0E0ZUdjb0ViRTlqaVA3RUlveHVOSE5STVpWYjJtdTVzTHplZTNlNXJENzhMazV6bHpld1hPRHdyOHBiY2t5N25hTmRhMi1zOWF0WThwejBQZkhqUGpkdEh6NDEtZkhObl8tb0t4RWU4RDE5LWhRY3pGZkZkaGUyVloxYkRQRV9FckdJSGg0dndSaGNudXllS3p5bFMyQm1WSUdMZ2xzN2lRc2Zta1NpUUl0ZnoyVjZFMGh4aGFWd09PS21tNkpDbWRLR2s3aGE0a3h4aHRxNHZoQi0tclpFTlRTamc3MHdOOVVacFVlUnFQanYyQ1RVcUQxdXZPMHRRTW5zQjRVSWxwa3lqTlpaaVJqVFNaY2FhRzZGRXpDUVczV3c3T0l0S0FBc1AxNnJDYklZVUdRTkZKcUpYSnMybDEzR2JkTTU2OUx2Nnh4V2dSUm1UQ1dKMnJSTEtvY0ptOTh6S0gtV0dKQjA1cVlaT0JZYTdQOVlrdExNZTYzUkRuc0RoZzM5N054UEJFT1pvenh4Rzk0U0tLSUNyUXJYMEJvYmcxRUFlcWFfY1lvMWpIcjUzTFhBV3J2Nno3UHFCYmRVSXM1cEI2cGo0M091MDhzV0c3UTVyREZkd3JHZEpmallLR1hQZnpJcUVJWi02a2xZeE94cC1kazlKT1dpZFdQd2hmblhyWFBsa1ZnOUhDMkdrYzVwM0ptMlVCWE9MQzRZM04tYTBYT1UwSHhjbDhjbHlwVEVpNExCNV9VU25sSFVDVERuS2h0TjE1WEZieG9obnpySDJRYkFKeWM0eUJKeHNZUDRJcVV6QTdBdklfYmpxQTQwX2UxNk5yMHB6YktIU1czZWlLTzBPSmJqa0JONnBlSjdveU9vX0FUU3Q1RGsyWW5DRnBHMDFnY3V4aFdpaE1NWVVzdm1Td2RQb05FX2xnazcwS3FwbG0zbVlTM2UzSXhCNnA4NFBFT3pnMUpwRUt4U25EcHZ4SGxUTmJzSXprUTlIN2Zmb2dLcUx0T2VySkg5NGZ0d3hVNGwwRXFTUHRfMHVFTjlBLTd3WkY4REhhOHlReW9wSUxkS0JOZ0hsN3p0dzJVOVN1TEVGeTM4Wlc5THllVWRqM0dDNjN6SURXM1pHM3B3MDVHSVlMVHQzdHI0NS1LdnZlaHpLYmhfQWxGcTFnaGV5bS11R3ZyME54cUVuTDJzdUFyUy1oS2ljenZiaFNuUFRuQTNHdkVaRWZkVDcyTW4xT2JTVnBVNzZSZlUxand2MXpVOUZ0V1l3dk5YVHU5bF94cmIzSGJVeE1vQ1l2cjJnQUZ2RXVHaTJwS0NEb1J4LWlRcmZlS1Q5Rm5xZmpic1RxQ0pseHViNDA1MjRuVFhTWVJQTnJscEhJaVhBMkRyUGtBUUdGakcwTG43S2VrY3V5ajF0LVU0T0dNM2JaYnY5ZGJPV1hIcVFQUGhrcmNvTTd0SFVyT1lPUmM0ZFdsX1JyNHlqYnRmQl9Zb3BtOHl4ay1uZUJ5VzJqUldaSHhMWC1vZUlQZWFBVFBvTE4wZURsX2F2bTNiY2hfb2xNOEFfNExTdjlDMzRwN3BkcnVtd3ZaWTYyNWpqTFBxVFBOMTFzQXRDVDdma3FUODZxUU0ydGkzS2JBRmVSY0ZtTzRvQjA4YzZRWDRCWTJVQ21xT0Q1OWV2RDBhanFBdnhOSEZ4eWVnS21MZERoREN6MmJIbmJUMlhqQU53aUhIQUV4SnRINmtSMGZiRGF5QzRZZjZsZUFva2xVeF9LaGNwUjY3Y29VZWN2MTJPa0NhZXhCZFJKTmI0aGUzTEVhQ3pOUGZ3YXc5MGVMRnE4RjlKSEFLdzgwNW9URlFyMkZBcWl6OEppMnRsR1RsTW80YndNM0FJZUhpTFZzU0xheWRJMzJRbWVzeE9ScG1ZNVRPSHZLMW5QaUduZGZnWkpNajlpMTdOVUlKcTRaeDhHN1BYWERJcWlseU95UWdvN3UtcTlhd1VmcHh3Z0tMb0pQNXVqVXZucWxBV2VPZ05KbXZNcTIxRU9RTUdZdWlROU9CUTJROXpyQVNXWC1BQW1qemZEUENXcW5BOHNUQnRTZ21FY3l5eDkzek5wdjBnZDdhd2l0LVBlVlZBWXpfRGxickk4dkNOVHZxU181ZWJwZmhPZmlkbDBJV1lBZEZQOXJVLVI0M1JOZWZ3bWM5V1pLek1RZm1HR0lsN01RSjZBQ1FORVNsYUIzdXl6RmVKeVdfQWFTMWh1RS1PVGVoVGpaWEtKY2VPSEo5MXotSFFQNEk5bVhrTXllRDh6UFZUVnVNN1BUNnRYcWNoR1FSdktIckVudmFUZ2tsRGt3bXZHOXI5N3FGQ1htRDB0Um9pSXFKN3lSNFlNN0ZLVWdaRnRpVEVvWEgwb0tKc3ZQQzZBcU9ZcXNlTVBWN0t3VGdZbUdjTWRFVlBCT3BBSkF6MWtyVTN3UVRtb1pDNlhhSlZ0VHptVGo1QzhFekdEMkFsT0VFSjNrcnJyTGVRR25hTFdhOHlPUzVWa3VpNjNvQ2RhZHdqLTZ5ejVtYTQzLXQ4Slp1a1UyWC1HYTAwZ0J2aVhWcnVuNW5BczFRMmwyUTlMWlVESG45UGdCd05SNWM0WUN4d1l0a3FmR1Yxb3FLSV9rQ3lSbWYtSVdTM1BLbFpNQnZYaHFzakIySFd4aHR3RlhJWkpPZ09YUkcxdDBaNGVERWo1b0tlMGFzdHNvNnpySWZ5dEZMMHZhT2oxZ1VjR2R2ZHc2UXRNLXJ2el9tNmRUWktLb293YmJpTmt6SGRuMEQ4Q25MU3JKUW44YmstWDJyTm42VmgzRUhZZWNBSE9BNWRyUHVBdy1WNnJXQl9FblV5ckJwYVVUVTV3MWhNYnMtcnY0OUc2bk1WYjZtTFVkU2ZCZURfdE9XQXptZXhVdXB1eGxkWTdOVGRfTGxKRVFxNUVQQWt2SXM5N3Q3WnlVMVZuYzJ5N2ZXZ1N2NkVLQmNmanZDWno2V0FjaVpPRkhWcm9hQ2dsM0NFQzFkNW1wcHluWUxyS3FRRDQ3OGRoRmNuQXp3WmgxNVl2NldzR2lkRGRiOEVDQmdOZ0tZakJ4YnpmZWZJNTRtaERrRWl2Yi1nYnhfZlJJTWV3MWdBb3ZKTWQ4ZnJRaTNhRExBNVFySl9ZNkpMWGtKdzNUdVNpV3dOX0pvNmRJT1lTTEZhdk9aMXZjaDNWaTJKcWVJXzAwbEs1TGo2MmZYNzhHTDBsd2RGRk00azVBUjZMVEl6bFlNRVJuM0dHbXN0cUh2bWxpVFkzaGNnNXlpTEx5dmRsWVB0WURQTjVRRE1NaGdWdFctUm9QMVNHbk16cVVrU0pfcUxYZ3BzQjNoanFkbUFZSEk1cXJDWkdyR1pieV9Ock9zVGMxczV5QlhGOVhkWkFDZkthWFcxdmpaTjJFSWEwMzRNOEw3ak5DdFdCRmhzM1VRMm9aZmJHQ2ZqbFdqdWJsV3VON2pBWDJTVnQwREppb2ZBZFJUZXhwY2hrU0dNRGJNR2daT29Yc0dJM3I4NUttV2RQOTN1WVJ5a0dxVVBnRlJxMjVDaVh0eF9BbWdVOGRmb0F3M29takNMcTZUVUVkZVoxaWZJZDFIeWRWdDFFZFlCU1Y4cGU0dzQyVnpxY2FONkNiYVh5T2NqX0ZicUJsV1owM0FES0oxaFM4Zy1wVDF0aG9SRWQ3U2RhYlFfUFhJVlYwMFRKR3AwdS1VYW5kZ21zU1BCYTU0QVVIQjBrbzNWV2ctdmdhSmU0WC1DY054SjREbmZzVTVzNWlSVm15aEZmajNBUzVTREdPNjlMQ1RoZUlhd3d4M2NwZDZHa19nTjdwel9IbFhCNTkycFA0TXcwTk9IUG5lQ1F4SlljVkpvTC14ZVJBWHMxT1VJMDItcy1tWWE4bTRGTzU5OVZwbnBWQzd2eEZybmluUm0tU09KSktSSDJYSGRBbjlTVTZlMUlVMnZJaE14djBjdHh3NHVWa0d1cUhnRW9xWWYtQW01dkpLNWs2dE1BSVI1RDAzdlBpLWtGY2w1YlZUNnVEOWJSQmVDVnZXdzZac2lydjdmMEdPcXpiTFljWDZVSEtOYXhPV3AzMWxnRndEbjFQSG1zWmZJaHB2VWJCd2tocl9VZFNFdDdRRjAzNFdsRkV6TVRTU0dpZ2tCTlltNTZ6QVpfY2VSaS05TG91ZUUtaS1aLTNBTC1vSzVfbXNmOTl3UkdsZ2pWb2kxYy1PUTZQY1hPOEZERHNTVEw0V25vdzdJUlByRnRhTnRBcDBEX1RQNmgwSGpqYnFjdU44eU40MEhvU1NXbl9HOXJTLVozRzM3cURsZkdEcXZyOWROaFB3d0twN1BHNDZNZWF4bkI5dGYxSEZ1S2h3dkk2MEZLMC1kVFU4b2pqRDQ3SS1teXZMaDJITy1hTU9PMS1iQTA3dkltT2pobWh4RS1uSXdzQVk5TURvQ1hFQTZYeE10ckdFQkNOVFVaQ1hTSW4wYmpUYTVKNlhXLUl6XzdLU2dhclRla045N1RMVzdFdEpUNXFwekhYajViZld6c2JKMWF5bWVDczFybFkyZjBLSnpLXzk3QmlCazlMVjdwU0VfX2JOcTFpa1hvZlVRZkFKekhaWDF0M0xRUzhTUVRhRzBsX192em9pb05NeWhTZ0tUR0dGenpabE5jdmRCRTJEeURLZ2VkZ09lZDJsd0MxTjg3bFY5U1pSNUdPbTV3ZjY5MmVMYWN6cEZFMGUzUHg4T002TlM0UkQ5QXgzNDhZcU9RS2tlTElFRjR1UnVzdTlGUjhDZ1Z4dXBlckxabFdaaWZIVkdudDBrdWdqSy0zU3FwZVg0ZlVlbS04LVZBY2V6LVNpbVc4Q3JybnlCSDdWNUlqamk1MEZUNjFFVUh4aE5JeFQtendlLTRlUkRvWUVhN3hwdUs2MDNZTlFUSU84a25QeHZaUE4ybjJoZkp3ckRhbkxkaVhLSUxWUjdONE9RcGYxd0RUbFplNXJuTzJQbVBNSk1zZHZvUDZ6ellYakN6SmtqQ3kyVTBnb0pYQ0hrWi00Rm9FZkpnQWJLNGlEY0lLWGIzTGZRSE9SQUFfTmx4THU0bmFpdElaUVdfNktqR0g1eXV6ZXFKdnVqTHVqam4tUXU5MGthOU5JaS11QUxxcU1QYTJ6S0padnJlNkJBTU5QTU1PODFFckRNc3lNTDZ6cl8zSWl1ZDE1dWtaRjVDQkpmSXZWeklxSldwanJiYTRldGF5S0dLUGZSc1k4dUpNU0daUTZSa0dra1RrWU1scnhlaG9tM2JzcG5QSkJqMVUtaU1paDlKVEE3d1JMa0RsVHEta2lKbDZQYkVielVsQ2RvRFNLUkxiR0RlLXEtM0xWeV9Wa1E0OXlrUy05Tm5GRF9PTDVFNlYxSG9JMW10dVI4SmprOWlsbWxmR25NVHp3emY1UGI0Wkp2Tjc2WWRZZTE0dnoxbWtPZFZnVTVicm9PNl9aY0NWQ3E0MUxVZVhmZTJDZlN3SGJfM0x2aW5ZMWZMMVJ4UFhIZzlwendIX0lINXFjRWFybUdQcFpVNUFKenN6dVNXQ0k2QlNEdmNpbnptN3pNNjNoUlFxNG42Z1U4NERJWjRhMW1pQXJyN05rbjlDVnVRdDJEWHVUcWV1N1l2UkNtUk1qNVFYenVEeHJzdGpfaHlIdHJmUDFNdmlxWnFMckUzbzZCRm1vZlVLNGpWUEJzZC1YaHMyU0J5dlFjVjlCQ0RjRGU5X0c3bWpobTVRelprMEZxM0l2d3BpWWI2bkc5Uk9vZzZRQnJQVjFwUExjc09TMm42WWoyX0RNLXFBSzNLdVVMamdXRlNMNUVyRzhwUldHT3ZWU0p2X1c3VXBLVHI5WXZGUVEzbWZyN0REYTVER0tRbzlfblRyVDUySkh0bTNSVUE2bzlqNy1hUUVidnBNUEx6eDRGSThGWWRtejQzMXlpMDhEbVMwVkU5SVYtRzhoOXpJY19yZjZsWmU1dS15bnVZNnJQUV95bGFYSGIydHpLb0Fjdkxpckw1RVJBZFNBNk96bEU1SVdZcTNhU2JPTUdxN0dCc3d2LUR2UUxlRGtBSWhQbHFyTGpfX1pxTHkxRTVrOEt6cllnZXBjdUc4M0I0SEFfWGp5RXRPMDQ3cEZma0ptektBckdOU2V6NFN4cENIaWdyNkZIMHpaVWYxdVNFNVFPeGliUVVJa0ZQV2FQcUUyOVV4STEyQ1JETUtQLVlpZjlWQTd0NjB2XzNPS05XUVlHZl9UcGRKM3pEaC1xR0NOZFlxa3d0eTU0TUtvU3BLNzZ0X1lEZ2NCcm9SNm15NmZtbVRUei1lSDVpS0ZvSnFxYWtoX0o5dGhrU1l1alNzYm1zckcwMmZidmNMMW5uOURRSUdVMlJLRGcwWWI1cVVoek1nTHBraWNjbXZ0SGlrQVFmSW9nZWVmNTMwTnhpb3VBOTNYSEd0dTVfcHVYVGp3REJzZjhPdGQ0eE1LMWZoYnFvZXpGYzVJRklxWWt5WXlBMXFCN1E3VmVwc1djSHF1cEVtOHhjendOYnhyeXh4dmpHaUJwbjllbW9HejdQc1RuWmpGRnVRNzZxcEhqblRqUENWRnFHdEp0N3pEZnBkcGZ0cVBYVk1sQldzT3NOY2pYVFQ4eUJWa0ZSbVN2akNzeE55OHZuZGFEZWh5dno2Z3ZRbXBzNWRGekhNNmNHOWw4M2ZSNWxwTEc1a0JQbXlkQkNiRno2NHhsX2VGVW0yUFVtTWI2Q1ZSaHBmNXJjZmxHWGRRWDlDLVBGSjFVSG1pc1JWMG1VM2xnekJuTWpDSzg4MFctNGZodldLTFZ4OW5KbEJObTdtWF9ZeGVMQUg2Vjh6VFA0YTVRaEVIUGVvVFJ4WlEyQTM4emEzcG5iSS1YaXZsOTE4eHBGVUN3UExpbGRjU2ZGQU5xNDk5OU43X1lUVmd4bXRPTi00YXJkWWZwbE9yRnMwbzRzbEhYVVNOUUtNQUFLNXQzbGFtcDVNaFZRUWExWFAwVk5SdV9KS0lZYUJCV1V5T01hd25vZDA2MEY5T2N2Qkw3ZVUtQlJkak5NeVgyMFRMT3NkWUQ5SXR6STd6RVFIX3l5R1FEdVFfall0X2ttTURTMl9FTFd6TVhDSUs4Ni10bGFCT293dnZGNlVpWlRDM2N5WG9Ic0ZoNzFjRC1MWks1VXg3ZFhRNmR4bUtGRDE5SG9uNlQtV0FySk5lOEk1NGpUaHU2SWZqX3M4VDhMQVY3QU5jMl9INGZzeHFuLXg5MjJ3T21BamQzNmYzMF9wMmc2VWdlQWZNVkFFc3czWG1uUjhhbGFLOTVkMnJKSF9fNzVOSDNIUjl1RXBaNV9sLWRhNmVDOGdvUDBLbTk1UU4zeEtVSWxjb0Z2d3AwTTh4QUZMelN0RUVSN0l5dDNGRnNBalVuNzFIWlVIM3dEVHpNUW5Bdnp5RWdsNmtkRnp0dG1PQ1NOclBGTXJpQ3FCejEtRXpYT2YxR0d2NXJ0N05aUW9VX0hjM2xUN2N5eWNUSV9JNDJQUjdmRG5RUHAyLXZJRXNOQXoxNF9zXzZxUktVVldoTEJWWUVnOU1NMXR3bkNkM0d5MjBKMmdvZ2xxbzdRa3JXYXp5MzNBdVdXMV82RWxOZHQ0OS0xYWNpTHNCQUZxaDdxcUM4eDM3bGR1MFhRTzczU0pYMDV1dzRSTk5fUHFXOGY1cnJFblhuWTV4cl84d1gwOF9EOHlXNjc1NkxFT2Q3Qk9XYmRCbHl2a2lhTG5naDN5UDlPQVQ1ZjV5cHlhc0l1S3lPckFnX1JqQUtPZzNUQm1UZjJZTGs1OUNxWFpnbWhKNHJrS1M5X2p3UXI1c2lXZnNFclVXbVFRaExueGZZNGVXMVR6NDdRNnEtSThJdmJHdm9NZEc4aGRESXhzczJTQmZLZ0lpZlB6NGpuLXp2TVVzOEZnTDA4UWpDVEFheHFHOFJYbVNqSW5aRERESHlzY3owSmRabHpITDJsZkNMMVk1eE9Ha2JrTV9YZjBreER4ODF3bVFjWTNXbjl0eFlFLXhFVDloNndOTGN6UVh6TzlONXFpSDZpNGJadlR0MkxqdmJrV1M5MEw0TFVFZ2puSE9PTVQtWmtYbWI2d3FoVzlfbjNvWkRZcjlNQ0xPWU5FQ3BUVzF0aGZtTVRDTkk5dW95V1hHZDdDVXAxaUo2UGRDT3lXb19vblpnaGVQY0JNUkhmdU9NN3pfRnJIdTlqNEN2Yk5nd1A2cnFvQnNWcFJMRUp6bHhZbU0xZFF1WnFheVlYbXBiOS1XTnhtZ01tWVd3TVhaZFZWWXJhOTJmSXJUVzdkU0lkeVpZQWI0YUtIZVlMdExJTEtkRjZHLWZiNmVVZTU1Vm9sMEl5S2I1X0xqTFhRLUtBUW92OC1RaXhUODN4N3k3cFZDeEZ4R3R3ZnBOLTFVaklfTDVEZHQ3N1dxMi1wN3JaNDFoOE9ER18yUmlOVVFucUFEeDc0WVlFX2I1SlA4MjY2SDhCSEQxVjFWUFR0aVFvUFNETF9kZTBFVnZ3dUt4Y1pRYnV1UnpzRUpXZHZfWkVJRks0dXdfaHdQOEV4ZGFpb2M5a1FzMmdJem9DVW1yaGFjSkZUU2J6YXI5elBteld2Vmc0a3ZjVUMwSVkzTTMxS1JhT0plWkkxQ2FWWmlfblZHLUYtbmFJQ3hIcm9tQUVBZDBEVFJJOTNBU1E3QUlIVE10bDlfQkxOaEo0UU1RQ0dSblFXdkF0SzhvbnA2NGVzcXBubzFfODktWDFzTVBaejlOMWVOOWR5UHBfZlVfOEpUdmwtVF9lRGwtOXJaS3duUnFOdHhHeEZuVXBNdXE0MEg5dThlMHhYbXJlOGdTTzhxZEtKeFpvMkNEUE9Ca2RGcF94Rk9GNFNCUjdQWGx4aHl3SWJ4dVYyRlYtd2FERzR6UDZCa1ZKdlZHejJSNkpzTXVELUoxdWJtc2hhQTJsR19fbXItLXVUbFlybGxLanFVUHBQS1BGUDZVZU9weFVWdV9CWlZBYnBJRmV4VVdxc3hrZXdxaGlFS0lSckF5Lk9YUTNscWkxdk4wNG1sb1Mxa2xPdVE"}' headers: cache-control: - no-cache @@ -77,7 +130,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:03 GMT + - Tue, 09 Jul 2019 20:18:31 GMT expires: - '-1' pragma: @@ -118,7 +171,7 @@ interactions: uri: https://vault96041739.vault.azure.net/keys/test-key?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/658aa7c54d564c7aa20e529f12a9894a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nRkoF6WTkMhvxLuxkozbNkz4uqgpJyCWMU8DkIme7oop9qyVQy1AejXbY0_OGjn4RpN-TRhJ7NxoFBb3Znq0FouSjp_a9GrUwVIfNF5kJ9yJSWnn_SpIB1ZwFxph8O3mq5YW84L6WQ3JZvjhF-IqDYmfEzjwlVrnBjIe-Mk6kT0BcXvaJb-g_EkbcmSrm09SKul_hQB7Ga_yPT-gj6Yupb1swEnDhEWcchTcAnkEwxiQSVbuFxE0nIv0U5T5x41n4NEGeuezOUjKA4hrx5ghROBK968DFAqjEpdfrYEBOgqn3urN5GnSnh6S3LmMRKMnGdSOhAhCY4HUC9XqsXibKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/7089b08b10a64ec68db241e20e2109d0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qbGcivUVxthKiCxcjxOGdsiItrjjTm0_-o0gkZmRSDlEY4ixbjJYBZMwZLxLm7_ZzoGTVTdON-_6bLu-ZwFqAAe2Nc_HDLkL3FTn1YkjTSA5I6FTXI-CC79m3mPLbrVNkHRUkN7QPpl9Qak8RyRl4TA5XtOp_ztKm5Lfuc1eJsosaRdqaMe2UlEbu0XCdYDoP1UoNRAhrUZwli-GHH6yWQxq0SBtnfUsXH0CkgprrnpDYM3dxUkjJJ6IRpW4acRXrFDg-ccCBQDRLty6eSRehugfZyTGciaT4fBcj245OjytQZHxz2ZGixRxN0YsvjoZXqquXKqfkKEcknMDNapyFw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -127,7 +180,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:32 GMT expires: - '-1' pragma: @@ -152,7 +205,7 @@ interactions: code: 200 message: OK - request: - body: '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLkUzYWh6U2FiTV9JQ19sUEhKUlU0NUptUm1PSklydkJJRVFmR1BCc0N1dlZHQ1hpQnJmZGd2ZTNSSjJCQXBvMWR6VjRQUlFpS1BRRDdIUElaWlRybHlESkJfWTVLaTM4Mm5OS3lrM3pwSU9hWXRDendXUnJmaHFnUUJUMEVxN0FsSXVUOHZUQm1VZ3pjMl8wZDlkdlBOakwzOTFSVnlKUDhjMGRCWTd0TmQ0TVJheXN2a0NfNElTbzZIRElmX2hzeG9KbGVrOGJJWExjUDZPekFkM09CeS1BQ0ltYkQ3N1NQRW0xbUtBd3I5UjVmM3lTOXlKVFFoZFExb3dZd296QkVHS3FhS0RNSzZENWRwRlRuWGJTX3R6cWlHVlJ3SXVRaVZSTlZ6Z21hZ3ZwY1JpTnJORW5vNUQ4enlBaGw1SFoxUVJ3Wl9nZUcwcFBTeU9SMkhTSVFpZy4tZmo2eXIyaUFwMWhoR1B3SU5MeUdnLkNXdE1UMVJDUzhDaVNuZWFidk8xQVRLdFF0RmFRZTZPSGZmd2g1emhFOTlCZkNtekM2RGk1Y0huMFpWS0JOc3hHY3A2ZGx3QkpZZmZUMHZMNi1QN2lUY0xsNzdwVHhfQ3pySm5RYmpCU21VTzBUQUtaMUVFMEFvUnpWY0ZaX25TODRTUE8zQ3N2RGFxYmNscGREdmE1cGNod21ORU9KZnUzM3lPOUhNejM0MXlLajZPaUNNYlhFaTdjSkZlQlJjUW1LWUpNQ3hfS0daOVFJdERsSzFLYl9HWWVFNmVZUjg2b0JvTlVBdzFnUzdSVDZiV09jNFF6MGZzSllVMUJTZ3NteWJRWFJWclhZXzN2TjdlaHNrM2NfaWxRTW05RU96UklGYjdTNFhsODJKVmFHLVlPU1ZSTmhuU0JWVy1UTTc1OTJkY1dVYjFqcnBMVE5DbHhSS09pR1NqQmk0UmFjZC1EanBySG5QT09FYTAxRWtYS3ZQcGlmZl9UUlN4ejJaa0pEdklWR1MyUTBOUmJoSDlkbi1ab2hMeHhpZUplRERubjFCZGhjUXlPQW5zYTRZbjNHTF8xSVg0ZXR6XzdOempNYWVIYU9JUXlsbTJkckgtVm5FUmdaZERLeDViRkxrOW1KX1htRU1LMkVFb0s1WW5VbUE5SnVVMU5PMDJxOHFDYTBxSnVmMWJoRFladWhVY1FmUFp0VVYxc3ZlSTY4c2NuajRUWFIwYzhIMlRPaU0zcWkzOXptTjNFMGVyczZ3aTBUYkw5Y2dUZVBJNEtoVkpxendKRUU2V09JQjZVTzZrSDFwdXgyRkVzeTJrLXVRQ3B5WUFFQUo3N2h3ZVdOZ2RvQUFwWUVZUHVOUmk3bVppejBVaEY1NDNUNEthVVVuazd0cU4zVGROQndVcENQYWF4c09OckVFUVMtWnZoMDlBcTJjZVJ6MkxIWjBud1dZc0FRZ1RyY3RTYWxUYkFGcENZM1BzT2pyVHBvdDhxcG90WlVZR0JuYkVUVDNKVTVxT0ZybU5Zb2ZUY01pcWdpR1hoR21kQVBZRm9GTUFKV3N6VTM4NzJySXppOFU2djVXYm1Fam1HZkxGLUVKWmFsVUIya2lOaE9nRmJGZ0IwVzdJR29qQUhXV2NaUTNtd0gwa0lEd0dZckc4RnJwTXU3NkFGT2xrZ1hadWdWRlVjdGk2eVpkbzRUOGx0V0JIdFh2b1BTR2I2bkFqT0xKWk1GaC1wejhTYmtPZUdsb2RHQXJSWmktTlVsYzFodkpwc2s4NXp4Zy1QRFpuMll2eGtYQW5ZN3NDOFRMaGR1a1dNWV8wRDNwMXFCZDE4T0ZkbzBKUGhWb0ptaGNSZFAyWXkzV21ZcHdUOWJsdWxTTGNzTklhbldHWDVHQjI3RXZMVjZHa1V0ZWtiZGNyUHZRZnRrY3FNNDJqTk03akNUQzhlOUZvSFdIakk2N29yNzlHMlo0bF82VG0xWG1sa1M0T1BvOElQcGZyV3BkSTRmd3FqeDVRXzUxRDNSMjY3N3cyeF91NnhNMVQ2TmlYbDJESXI4ZUx5QXNMeW9TZGhVV21CektvcUNhdHdOQ3V2Y2pfVlczTHMwMzRMV2Z3VGpPdGw3WHUweWxneWtvQy0wNm5RUzhzXzZlcXk5STRtbC1kNWhMbGpZSFlUdEFGZ0d6Z2RFMFFkOC1tbVF2TkVuaWRsWjczWktpV3Nrek1mRm9kQnRIWTZVWUZRQTRBVE1qZTRhdGdIa3Z5OWZpSHhYZHcwSGItcXVxVzI5X1RSVWJIYnc5aWhZVkNpRHlPUVJfbXBOZm54Z3hNbjdkZFotNnZXNlNNTWQ2Nm16TE9sWElIZjFOY3V4SXQ0eXJseGVfV1JnbFVWSll1WVRHOEpsVEE0aWR6bzN2ejJzR3lVcWV3enJ3S1lHZUV2aDlTalFvM0pPaVpoR2pUUTNUa3lNYVdGaERKWkU4UHdEUjdpclJXTXhUZ0lYR1VPd2haTE4xei15RTN4WXBUNXdQY2tLSnRfVnNXckFTZGs4U096VmRXV2ViQW0yYU9HWmplanNXbG5iNWI4SVkzanJCeF9GREd1X0M3Y3Ribno5WVZDalRxbGc3QlA4MXg2eGdXb253WF9NendSek5Ua2NhdExqYWdpSDc5UFNBdTg1SlJGbzBZd2RRai10N0U3b0tSVjZyUXRhNktia2JrcnREWVdBZ0ZoUHpSa0NPSWt5bHV1T1M0TllqYXh2ZWVFS3V6T0I2SUhNeVJnRWVrbzRNVGJmOWhTa3VHdnNTMnJtOU9JNDdFZ0lZQXg4MFBPcFpkcTJDTF9FdGJlal9YdjFOeUg1eEd2Z00tUkk2WmNTWWZ5Vld2ZUIydUVYeWpRM0ktWEwtemZSMGc4ZDVoeGFSRzV2dWdCN2dKRnJVWHZGZW1jWk1NUU80MG1OdWdlOEpBTHJXMjJUSlE1bm5PZWVVaUpHb05NRWY1OWRPN1VpeGIzSXBpR3FnVUd3Z2JCRXJaV25DQWtDUVFOUENESHlCcjVTZEs5Nlh4Rm5LVVV1dTNfZ2UtQXVyTFVxTjYwdXlaSGpheEM4dzJQV0FZOFpBMkw1ZjhIXzVILVcyeTNMRkcwTDQzOFFBcF9mZ0UwYlhZZGJiZTBLV3ZCUUdhWmk2U0JCQzY3UUR6NkcySVV5d1hfTVZWcXlod1ltMFY0T2V5TVJOdXBTVWlsM3NUYzEzZW52czJHcnVYRDcwOF80MjBUdGJ3U05DUVB6VXFWUnJOYjFER0VrckJlcmV6LVBSc0RuQk5IbWFheXZNRnJfcl9ZNHREV0xZVTZ6M3U1X3dCS0sxd3U1THdCbkdMR3FmbDhPY1RyWGhVSHVEYU15bGdTelZBVHpoTE00NDNfWDk3TU9wR1EtbC1CTmx6NXZkYms4RllLUl9lblRoeWM1Mno0cnpEVEhjTi1ST2hldGpOZjN2TE5ZcWpzRmpaQ25OanFUc1hsQnFDWXZTSElXaUs3dVJEQ0dlbmY3dVBkNUgyLVNpQ1hCbUFrQURadnlBNExCUEJiOU9SM2dzQ0dRRUJBYXlaTmk1ZDZvVjZvT3REMXlmRW5MZ2hhUXNOaG9BNnA3MVdJTjhEakpUeGFsWHo4bGN4a3lqM0syU0FBTUpzblpldGdYakdlbk8zWjVZbzZHSmUyTUZxU3dkX1k5RGRqc09pMndzRlVKMjhubko0Wnh3N09ZaFkwa2FmV3hjWU10WjlyMGVjVTBIZDdiZXNjbWlHalJIbFVSV1QwS0czTDUweGFrU2c2RzAwVVdxR21jcUxuRllSdmlpendDbl9yWWN5UzNYSF82Wm4yTk1VQ1dFX295RkNEbFYwLUJ3clpGLVhKdVRoRmluRF9MUXpLeWNxcWV1MlZmN0ZVV3paWV9SdUZBWXcxb2ZSZzRtZkdIa2VldllKN3NQX2ZnSFBMTFZVSHZaX2djM25BOU83d0h0LWdiNElHYWE3blRSb3ZkanFZZzRjZWdnNFdBc1FVWjBiZXRHLWUtWTAya2RYd1BGRnZHbVYya3M3ckE2U1VmNHBDX3E2cVZWbW5xRFpVQWNBaUMzUWFKalduSUluUmc0ZzZxSW9yaDFLWFVoVDJDNWFEQlpQc2x6b21JNUZWMkE5bzBhVDB4ZndNODl0UDdrQngxRlYtTmdERzN1a3M0eTFRVTkyM2pNZnotU1J1a2h0R2lYVG9Oak1pMmJyVV9sdHE1M1hTVEptTU1HakNjUXQ3bmhiM1YtLS1NRWhXMy14RVk4SldDbXlMYTFaajZ2MG1ORGpNOFB5eWxoaFh4NzhIM3RPcjdtWnM4Ni1scmpfNG9mYmNuWmo2bFhqWGhBVVBIZk1ZRDN6M1FRWHRIREFvUUFDVndNY2g5V0JqYTIyZGdiSzhmelRTZ0R3SW8xNTJtR1BUMW03dmZDbXkwdHNvY2Q3c2ZMYlUwOWdqWF9fTEtQckxLNTVFV3VBV2cwTV9BU3ZBdUZqdEoxV1J6MlV2eWM0MTdNekpxRUdfYndiWlQzRVZkYjdKdkh0QThxVEQ3X0d0Qk9lc0FkRmZsMFVLSTA1aUNPLWU0cjJvdE1PQjFYMlZNUVRKQ0drVGxjd3ZudndfUkMwT3B3YTZ1SjQ5aUZaQ0IxZzZhQUhqUS1lWWtndGs3NnpBRXBkR25uWVQ0Q1NmRW1ZYkprZmg2M3N2UllreXNWaGdsZlhZSUFjU0ZDUF9vZFczMU1vblU1VUd1Vkc5TGI1Z2d4MEx1ZUlXMk1wcDNhTEp3ZkoyQnpPcndIRWhhaGZjaUpTWkV1LUU2YlJhNnRERkxFRFRiNG1TVC1yRTA3R1JlN29LTjB5X2ktZ2p1Wmx4T0xsTnFKTVpTUmFndEV3N0loamoydjJuMmNQeUZEYkhxYVRVOVNtUU1sZ0RFelFrNHNpLUxyTE4zaUkxb1FDOWVMUGZGWGhOQktWVFk4dGNwLURMR1Z2aXY4Y0ZMNmhXTlNKQ2c2c2ZYb2dDUlBLVmw5SHVGejcwczk4bmdUdE0xV21aeW9xQ0FEdXNMU0xGcEY5SXRnaUZ3T3BCWng4UWpNalJNZmI1eW9IUmVHN1JmVFBjZUl1VV9jcGdQajNRcVBvT3ROb1YxbHJ4VF9KTFBPekpVSk5nNzhCZFQ1Sk1wLUtzbkNsZTFOZ01BQ01CandxMTQ3aUtOY3YwMUhGb0xPT3ppMlhTTDJqODdXVjFuQ2tvc0J6TnNEM3lCSkJ1bWZublRIQ2dULTdmZnJRekFsLUxmb18tMlRPTWdOTm1PT2NmVHlxUWNXRXBWNTVOMGkxc0NpOVBlaEN0SG5lUnp5Wm85bWxYUnZYREJ4R0VBSER1VFVZZ0dEMGJUNHBrMk5NY1M1dmlEQnhOWTFEZEprbXlEb1J6Q1VQMm5vX2VxRnFDX256NXpHbGJ1TGlBajBIb09hejVRcHlvcklQZXhqdHZXOW9oenI1c1lFbWFjRFIyMWNPQmpLMjZvQzR3M19UWUJtLTVxUDVzdU1nZExqSVNlY0pwLVRYaVRLYUtPSjlIQndoSmtGQk1vQjkyQVNid2dSTTlvTDVhRkFNLUJrYVE4cEd6ZHMxUzF5Tno1TnFwdTVoSTgycGhBaVFvT2tFOXRnU1MxSHdFd0VYRUFkUWU0QmU5ZEw4RGotYkdXYXlWSjlwN0tMdXVmTzRzWV9JSTA3MXVrWG9FdmFIQU41cUdyeV9iTW1FWXlfQkxKR2tyd3J2ZVlOV2pQV2ZpeDNKSjd6V3FYS3ZHWHA2RFVGTldJYnRraFN6QzB6eHNXUXlSZm0tbGR1Nmc5cHhIdjVta3pOeFpNT1Y5RS1KUV81M3lVZ2tfLW52RUxDNWFHc0dleEVaWHNJQkdnWXZmaGpfdml1dURkdFhsQ3lFTi0wSXVyaDROaTBNXzVtQS1McFFiME1qSXBOZUVlaHI1OFFuSVFfQjhibktILWliWFdRcUo4TEVDUXhUbzdGVzBBVjFBWWtTaU1yazZhVHFwQ3Fvc3IzQXlnelZYWFp4eHJhYTdhTEdVZm8xcHQwbEwwakhoSm5Qd0hOSnFCMG9SUEh3VzhHSjR5dnM5ekhuVFVpbnFvQXNzZE5FZXRKa2dTMHNxX1R3NU9HekFJUkRvcGJUdjlhb2UyYTROWlZ1SDBacjhLYW40LWRkQ3FVdWJzTlV0VURqaUlBOEtWODZCTm5SRXBQbUJIS3pPcUg3Tk9UYUFoRzM3TEJlZ1V1SGNEb05YeTlRc0t3TnZjSEptdEN0Q0dJZHFDMS1QOWJLbk16LVV3LUFWY3c5N0tFODdvai15Y0sxWEI3eWtYeU81QjlFcUJPczk1cXZzaVhKWFJtUkgxSDVHdFNkc1hpNmh1NGpkemRteE1kTXZURUNYQ3pPZk5tQzVJNTNVcUltX2tsaEdGV0RYZzI1S05wR054ckpXTFpGV19nZjI1RldOS0daNTZUeVBRNXFhLWVsQzN6NXV1WW5WdUU0RXVRcXNFbE1ILUV0eEFHN3VwM3BsTzBoZW9qaFdzTVc4NlNucG1Xa0I3bklwcllUUzRHVmhmWnhoRHBRUmtSbUdfRG5FNnBDMzc2Q2xtbDF0Q1VmbHlqbnQwMlJxc3FZMmxTSkd5alZ1SkduMVlybTFBaEVGc09GLUJ1UmRvRHEwZFJscVQzREV0RVV1ekxQZHdGSGhhdWh0N3JxeTdON0tfY3hmRDBfS3dmYUt2Yk55ZXBHcEpTRVp4bWs1TDlpSHJHeDdCaFRDTWw5TG5FVWxVSVc1REJYOXVWWnl3Rk55NWRnTW1uU1VFUW5GVEE1Nk5SWFAwajJGbjc1VGdGdGtldXNuREdRT2hjbWZwbWkwSVhfYklrdzRUaTVIV1pPb0NPMTdhQzFYRjktT3pQYV9pdmlzemE4bmVLeFNueV84SFdrSDNRZ01EV1BnQVZlTUVmODNILUhfZXJaQk8yQmNwdWprYWtlVzdlVEJKekU4dVNGbEphZ1dqX3ZjdzVMeGdyUldZc0YyeHltX2lKZVlVNUF0bC0tcFVBNDhtV2dEQVcyZWswVVVSMl94YlYtc1dEX1RkVEVqeTFDM3M1UUt3WElubFRFamNLX3VmbnJodVNfWXRULXRudDE3R05NZUdnQTRxR2VVbGhjdkEwczdDcnVkRzJ3c2VMWnVJV0FhNVJBY3J0WnZscEhGMGs0VzJSR094TVQ2U3BIRVFDcXFEa05Ma0p2dWNxRmgyN09QXzVPeVlnaG5DTEZLRVNKSVg3bEpzSFhmUG9WVklYelA1NXhTeVBjWmRyVlVJdzBQZ3lJcGZHSHU2RW9iWVVKOVdCVE55UWlhbFpTVENPNlBhaHZmUDhSWnVoNEUzcHR5N0xpSXE3RWFEUnIwTGlhdjU2UXIzcm1ENGVMZEJyR21oc2JBRUtOU3JjNDBZQndvWk96T3htOHJzeHdBMjJDZGVFaEtoTk1RN09PMllWSHp3bm1kSEFvVXdNQ09FQVN4U1pjLUVudUI3UThWVmx5SnBISy1VZGdHTmxmRW50Ml9ndG04VjM5b21abTJ0VE1vTE12SFJadGRzdHdVWFVaaGFESUFpTDFoV2luMDRFRUp5TlBsOUhzTFNGR0hCU1VVMXREcTJuTUswNHQ4bXdXVDRlZ3RNcTZ4SGpvU3NzRm9tVHBnMjFXSkt5RWhHZThaVi1PSE1QeHhFRmNBWWVTNXFzR2tibXFMVXgzVmFqblk4WVdocTZkcnZHeEliYWxod2xFcXVDSXJPampQT1V4c2ZwVzcxQkR1b2ktLUczaTNENUdsR1RiLXJnNFduWG5wSHRvbDloaXl3Q254eEk1b0lNMmVYSXlyMUZfbHRPU18wRl8yZVNYbi1lZVFENHloaUJqM1VxVDJlT1NVUi1aWW9EQmY3VWlQTU14eXhsdzM2Y0d5M0lLcUtvRzJieklLMkw1OV9UQWlsLXJuWUwyOHBUa2hDV2ZBbE8zTVdWSEFiS1pTWm9LbFBzUldlb3ZQczl5ZEMyVTdSUkl2OE0zaFkzT3RoWVJKNUVhenV3U21IRTl3X2lhOUJwdHVJYk43eE5IQ01VVFNWMWJWekc0SF9FR3VwRUpyaXVvNTN0YWxZX0lRSnFhYXZuQl9QLWEweGE3aDUtY05tSnpaZXNTekVFNm1PUWhzTVNWbTFTVEVFMFA5Sm5RblNiOGp2aVc0Q015Q3VWS3FVNFdvWjN3dWZmUUdDYUg3ZkUxVjZyc2RNek9KbzIwOEVXSDZvTGFXLUJseU10UjhfVWR2TEYzdklTZ004TTlqZTR5LS1udmY5cGJFZFluTzl4SFRDajEtN0xRVW5ZVUYtSk9LYjFLY0ZRMEdpUnVFaVhCNlA1Y2dRSmsyWHRQaWtYQ3VXOHV1QTdGSWdxdmR5eUlmWUF0VW9tWGdXX2NnQXVEbGExSTNSQkJrQkpkT1BGU0NyVnRKSmQtamJSY055aTFfTzdkSnBvTmpxYjZGTjMtOFF4cFAxUG55b0pYS2oxSzFPeUxRaThRY2htUDNCeUNHN3ZuOGVXWkQ2b2owU19vRmZEV010cnJTamhwcnZjMlI5dWxiOF9FOWxWeHh6aGU5cFBDcmxmYzBqN1E1N0ROSWM1aFhaQXZhcHdpc3VGSTNnczlMaGZzRzY1T0VBZFlKUkFiSmxFNUtsMEwxWE9RSVBkUi1ZQ3p2V3FndjAtRWFGOVFlWGpuaFZXRG9GQzBSaHo5NjFjMmx4Mi1VNGY0Nm5vdXJjUmdsTi1XdUlLTFM1S2JsZTJVVUlNUUVCT0l2dVVaZGllMTlFaktqR3RjbTJ5b0VidWIwX3M3U0duZkotb1RIbHpKMDlDMXRuNkN6U1AtUHdIVzZvVnBSTnBKcGdFdTFuaHEwVXY5bzdiRmF4Wmg0a2twTU1IamR6T3VQMnZiTGtWbFFULVR2U3EwVVF2cWxDQmstTHpFVzUxNXUtUWZDNVN3YUhMWnVIMW1FOGhoS3NYcmxsQjhzX1VPQTlkS3VaVGl6TkJQbXlHdFlrVE9pOEFpaXVfS0o5VVJhNWZHLWtiM19BZk9McVpmY0lmbzlTNm4tTlpqWmM3T1NvaW9wTXJjY0pjcjVQRWZDd21LRnlxZVZTM0tlZkRrR3l1bl9MZVBWVVJWQmNnVkE1Q2NUcUxUUkFoVC1iVGRVNTNIdHdJbTctbGcyTnNDdWQzaVg0OExKVXlBNzNZNXFnZFpFcnpzVmRua29YVGJ2MkxDSTNOblI2UXg0d1h4ZENabE1aYVRfaDVtQlZfY2JHXzF6S24xWjRMbDRfNVFDUDZ6cXh4aDZGZmtDWlNCdVltREdNVENrbDRhS0tQRmpQWDJMLTNER09VTk5fbnhMYjdObmc5RWNfUU85N21DRmd0MVJCLWUyMnc3UlFfSFEyaUhPdWN3N2lHR0VIRTdyY2dVSk8yeWk3c1lNSFhNUGY2MWd0aWp5YTJKRm9LYTRZR2J3ZjBMTUF6Wk5MZnFpTEgwOVYxelRncDB0QV8waUFXM25HSTYyNVRnQWNQY250ZzJzRVczVDNpaVYzcnRXZGx1V1RhRzBMd3JlZGdXSElteklUN0c1VmdCUW1Cd0c0MjE4VTRPcmZqX1k4c2VFcllfalRMUmxPckJHQnBvRTZnc29SRnNqYkhrd2tCbHNlRzNmNnBBOExsc3hQMjEtVWNsNDYtQmljQ3JzN3Fkdjc3QVZwMFJHU1lJSVRHNmFOTFJYMlFxZUVsdEFhVGJDbHNraGV3bzBVYjh5QXpnTkI1LVVMa0tqSHdqRnRBLWlWd1pPU2k2REYySlBjLVFUMUctYk1UcVZmSm9Uc2lZakI4eDNKRWhhVC1CQ3E5ZXl1SWplUjVFZlhqWjlxVzJfZUl1LTduZmtvbEZ6dFNOZ1BIOTZqblBzY1ZjY181UWluNEpqeHNkVEZTWWtCak1TdjB0SXdBbFdGZVNJd1otUWNkOVlhOXFJNzlpVlFmUU54VkUzeV9YU3FrckFCNnBnWW1jNVk4VGJWSUdINS1HWUFaZGdxQVZFb1VMdldyZ3ZBNUNscG5fTkFCM01DMEk0Tnk0NXlnbjlGSUZjSUYzN29GczNGdUVsOEpCenVYSUVDR2VQNFI4VEYyb29aZ29jeEFndEY1TzNaTjMySE5aWTYtZWxZZm5SZW5hbS1TaDBldlp4UnhaWFZLT1Uxclc4Q3ZvWFN3VU1KQmhRdVV1TW9CT1o2OXgzcGFNQU5kMnVIM0VnR0x5TFdYTFIzQThiRnFHZC1TeWpiSDlqTmw3SVNVYk9zVnQ1b1ZTdDZtMjluNnlEOXBMQ2JVSGlVc3ZRYmxaUThpQ1JLeEpMYmtqN0dISWx5cmgtQXZ3aFYzakU3Ulp5VU10c0loSm1DNVNuelNHLjE1MkN6c0pmY2daV0F0MGVxcWRjM0E"}' + body: '{"value": "JkF6dXJlS2V5VmF1bHRLZXlCYWNrdXBWMS5taWNyb3NvZnQuY29tZXlKcmFXUWlPaUkwTXpnMVlqQTNZaTFrTlRRM0xUUXlaVFV0WVdVNVpTMDJNVEJrWXpNNVpHWmhaamdpTENKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V4TWpoRFFrTXRTRk15TlRZaWZRLk4zTkxnQUltVllzMTBLb0R6T2ZkcGI2aHRhMFBQNGxQOXh2VWQ5dEJEaUE4X2dPa1hQcnFza1dMTDd6d1psd2FOYlZNbFZpbnVzbmVwZndNUnlQdENmaHZUSjBMWG5PdUh0TmJfcTNpY1F1LUt2UnhfTHZpaGp4bnVCMUtKNW5ac08xR19RbTVPLXEwdkZySGZJNE9ZQjhIY01fR1VaOGx1dlFYWW5aSl9mY3ZaREZmazlWdVRzaW5fQmxCbXN6Q0JrdnA1LUFrMkwzQXFDUml5TUNjNTMwTVFPbjI4eElXTEhUcUdBTHpLM1pRRTNFMTdqVk9iT1ZtN2ZoWWh5Y3JHYjJWZFpQdmkzR2Vmd2xQZlFNal9VUlM3SzNUc1FkSnRUazREdVdsWkZJTThHY1R1NjZ0dDhIblA5LUE0Vjd5RTliQWJnWXlCZG9QUnhzem01TVg3US54dXBZUjhXMGdvNTI1eUlCSVdsUzF3LnJ0MHZkWFFDUlAyYjJ5b2htdk8wdlRjY1pKLWVlQWNlVHF2WVpEd0tqX1R5SE5iV0RQcFQ4ckN1Z3JpcVJ6ekFvY1hNNDZWME1Fc1JFbGNKTnBKNVNPR0pzeEQ0dVM2c194aDlCQjgwUkRxZTRHcllGckpsSlc4Wl9GLTljMGI2dm54aEhvck9vQ0VxS19VU0xTcU1wNE5pS05CMlZ4aGV4My1NSldJS0Q3M1BxaTZQdnU0MVZnUlR1NGlqTmhMeDJYQ3NtRFpxZkVuUGZHeHFQcWdVYTVvRm9uYUh1enNyRV93LVNORlhlWFQ0Skp2SWkwTFNMRDF0d25CTkVpNUo0Qk16TzJzZExJODhWX001SlpBLXp3RmtGRGNHUnJGeXJkOHVwaDhMVmFrMUpKZExvWkxocERmRjRpQWVLWW0wT0ZtdV9zNEFzbWJCR3ZuTnpUS3IxNlpNZlNrcUtuMm5uUHc2aV9JN3FzR1lEX2xEdm5KaGx2cmQweW0zb1RlSEFRR010OFRZRFhqNXZTejUyNExyNC14M0JMaWx3QlI3UF9SQVhfa2d3LTFYbjdGa3pGM0lscm8wMmNsSUxPWDcySjJUT0JRT2ZZVjRyOGxscE5Bakt0SEJ2cFFRMXV2MTRZbkhhN2VKTVlWWkEwV0VnSlg4elhJb1Nwemd4VnNLSTlRU2ZQLWZQVGdMQ3VUNm1iZVBHWWdSczE4dDdjbGhJaTJwQXRtaVYtam5GVXZJMXZwbWJzekg4SDAzYmZxZF85QmpHbzlzRklNVTBwZjR1Rk1aSmpfUnBldU00MjBmak91clpNY0xFV3lYQm0tU0E0ZUdjb0ViRTlqaVA3RUlveHVOSE5STVpWYjJtdTVzTHplZTNlNXJENzhMazV6bHpld1hPRHdyOHBiY2t5N25hTmRhMi1zOWF0WThwejBQZkhqUGpkdEh6NDEtZkhObl8tb0t4RWU4RDE5LWhRY3pGZkZkaGUyVloxYkRQRV9FckdJSGg0dndSaGNudXllS3p5bFMyQm1WSUdMZ2xzN2lRc2Zta1NpUUl0ZnoyVjZFMGh4aGFWd09PS21tNkpDbWRLR2s3aGE0a3h4aHRxNHZoQi0tclpFTlRTamc3MHdOOVVacFVlUnFQanYyQ1RVcUQxdXZPMHRRTW5zQjRVSWxwa3lqTlpaaVJqVFNaY2FhRzZGRXpDUVczV3c3T0l0S0FBc1AxNnJDYklZVUdRTkZKcUpYSnMybDEzR2JkTTU2OUx2Nnh4V2dSUm1UQ1dKMnJSTEtvY0ptOTh6S0gtV0dKQjA1cVlaT0JZYTdQOVlrdExNZTYzUkRuc0RoZzM5N054UEJFT1pvenh4Rzk0U0tLSUNyUXJYMEJvYmcxRUFlcWFfY1lvMWpIcjUzTFhBV3J2Nno3UHFCYmRVSXM1cEI2cGo0M091MDhzV0c3UTVyREZkd3JHZEpmallLR1hQZnpJcUVJWi02a2xZeE94cC1kazlKT1dpZFdQd2hmblhyWFBsa1ZnOUhDMkdrYzVwM0ptMlVCWE9MQzRZM04tYTBYT1UwSHhjbDhjbHlwVEVpNExCNV9VU25sSFVDVERuS2h0TjE1WEZieG9obnpySDJRYkFKeWM0eUJKeHNZUDRJcVV6QTdBdklfYmpxQTQwX2UxNk5yMHB6YktIU1czZWlLTzBPSmJqa0JONnBlSjdveU9vX0FUU3Q1RGsyWW5DRnBHMDFnY3V4aFdpaE1NWVVzdm1Td2RQb05FX2xnazcwS3FwbG0zbVlTM2UzSXhCNnA4NFBFT3pnMUpwRUt4U25EcHZ4SGxUTmJzSXprUTlIN2Zmb2dLcUx0T2VySkg5NGZ0d3hVNGwwRXFTUHRfMHVFTjlBLTd3WkY4REhhOHlReW9wSUxkS0JOZ0hsN3p0dzJVOVN1TEVGeTM4Wlc5THllVWRqM0dDNjN6SURXM1pHM3B3MDVHSVlMVHQzdHI0NS1LdnZlaHpLYmhfQWxGcTFnaGV5bS11R3ZyME54cUVuTDJzdUFyUy1oS2ljenZiaFNuUFRuQTNHdkVaRWZkVDcyTW4xT2JTVnBVNzZSZlUxand2MXpVOUZ0V1l3dk5YVHU5bF94cmIzSGJVeE1vQ1l2cjJnQUZ2RXVHaTJwS0NEb1J4LWlRcmZlS1Q5Rm5xZmpic1RxQ0pseHViNDA1MjRuVFhTWVJQTnJscEhJaVhBMkRyUGtBUUdGakcwTG43S2VrY3V5ajF0LVU0T0dNM2JaYnY5ZGJPV1hIcVFQUGhrcmNvTTd0SFVyT1lPUmM0ZFdsX1JyNHlqYnRmQl9Zb3BtOHl4ay1uZUJ5VzJqUldaSHhMWC1vZUlQZWFBVFBvTE4wZURsX2F2bTNiY2hfb2xNOEFfNExTdjlDMzRwN3BkcnVtd3ZaWTYyNWpqTFBxVFBOMTFzQXRDVDdma3FUODZxUU0ydGkzS2JBRmVSY0ZtTzRvQjA4YzZRWDRCWTJVQ21xT0Q1OWV2RDBhanFBdnhOSEZ4eWVnS21MZERoREN6MmJIbmJUMlhqQU53aUhIQUV4SnRINmtSMGZiRGF5QzRZZjZsZUFva2xVeF9LaGNwUjY3Y29VZWN2MTJPa0NhZXhCZFJKTmI0aGUzTEVhQ3pOUGZ3YXc5MGVMRnE4RjlKSEFLdzgwNW9URlFyMkZBcWl6OEppMnRsR1RsTW80YndNM0FJZUhpTFZzU0xheWRJMzJRbWVzeE9ScG1ZNVRPSHZLMW5QaUduZGZnWkpNajlpMTdOVUlKcTRaeDhHN1BYWERJcWlseU95UWdvN3UtcTlhd1VmcHh3Z0tMb0pQNXVqVXZucWxBV2VPZ05KbXZNcTIxRU9RTUdZdWlROU9CUTJROXpyQVNXWC1BQW1qemZEUENXcW5BOHNUQnRTZ21FY3l5eDkzek5wdjBnZDdhd2l0LVBlVlZBWXpfRGxickk4dkNOVHZxU181ZWJwZmhPZmlkbDBJV1lBZEZQOXJVLVI0M1JOZWZ3bWM5V1pLek1RZm1HR0lsN01RSjZBQ1FORVNsYUIzdXl6RmVKeVdfQWFTMWh1RS1PVGVoVGpaWEtKY2VPSEo5MXotSFFQNEk5bVhrTXllRDh6UFZUVnVNN1BUNnRYcWNoR1FSdktIckVudmFUZ2tsRGt3bXZHOXI5N3FGQ1htRDB0Um9pSXFKN3lSNFlNN0ZLVWdaRnRpVEVvWEgwb0tKc3ZQQzZBcU9ZcXNlTVBWN0t3VGdZbUdjTWRFVlBCT3BBSkF6MWtyVTN3UVRtb1pDNlhhSlZ0VHptVGo1QzhFekdEMkFsT0VFSjNrcnJyTGVRR25hTFdhOHlPUzVWa3VpNjNvQ2RhZHdqLTZ5ejVtYTQzLXQ4Slp1a1UyWC1HYTAwZ0J2aVhWcnVuNW5BczFRMmwyUTlMWlVESG45UGdCd05SNWM0WUN4d1l0a3FmR1Yxb3FLSV9rQ3lSbWYtSVdTM1BLbFpNQnZYaHFzakIySFd4aHR3RlhJWkpPZ09YUkcxdDBaNGVERWo1b0tlMGFzdHNvNnpySWZ5dEZMMHZhT2oxZ1VjR2R2ZHc2UXRNLXJ2el9tNmRUWktLb293YmJpTmt6SGRuMEQ4Q25MU3JKUW44YmstWDJyTm42VmgzRUhZZWNBSE9BNWRyUHVBdy1WNnJXQl9FblV5ckJwYVVUVTV3MWhNYnMtcnY0OUc2bk1WYjZtTFVkU2ZCZURfdE9XQXptZXhVdXB1eGxkWTdOVGRfTGxKRVFxNUVQQWt2SXM5N3Q3WnlVMVZuYzJ5N2ZXZ1N2NkVLQmNmanZDWno2V0FjaVpPRkhWcm9hQ2dsM0NFQzFkNW1wcHluWUxyS3FRRDQ3OGRoRmNuQXp3WmgxNVl2NldzR2lkRGRiOEVDQmdOZ0tZakJ4YnpmZWZJNTRtaERrRWl2Yi1nYnhfZlJJTWV3MWdBb3ZKTWQ4ZnJRaTNhRExBNVFySl9ZNkpMWGtKdzNUdVNpV3dOX0pvNmRJT1lTTEZhdk9aMXZjaDNWaTJKcWVJXzAwbEs1TGo2MmZYNzhHTDBsd2RGRk00azVBUjZMVEl6bFlNRVJuM0dHbXN0cUh2bWxpVFkzaGNnNXlpTEx5dmRsWVB0WURQTjVRRE1NaGdWdFctUm9QMVNHbk16cVVrU0pfcUxYZ3BzQjNoanFkbUFZSEk1cXJDWkdyR1pieV9Ock9zVGMxczV5QlhGOVhkWkFDZkthWFcxdmpaTjJFSWEwMzRNOEw3ak5DdFdCRmhzM1VRMm9aZmJHQ2ZqbFdqdWJsV3VON2pBWDJTVnQwREppb2ZBZFJUZXhwY2hrU0dNRGJNR2daT29Yc0dJM3I4NUttV2RQOTN1WVJ5a0dxVVBnRlJxMjVDaVh0eF9BbWdVOGRmb0F3M29takNMcTZUVUVkZVoxaWZJZDFIeWRWdDFFZFlCU1Y4cGU0dzQyVnpxY2FONkNiYVh5T2NqX0ZicUJsV1owM0FES0oxaFM4Zy1wVDF0aG9SRWQ3U2RhYlFfUFhJVlYwMFRKR3AwdS1VYW5kZ21zU1BCYTU0QVVIQjBrbzNWV2ctdmdhSmU0WC1DY054SjREbmZzVTVzNWlSVm15aEZmajNBUzVTREdPNjlMQ1RoZUlhd3d4M2NwZDZHa19nTjdwel9IbFhCNTkycFA0TXcwTk9IUG5lQ1F4SlljVkpvTC14ZVJBWHMxT1VJMDItcy1tWWE4bTRGTzU5OVZwbnBWQzd2eEZybmluUm0tU09KSktSSDJYSGRBbjlTVTZlMUlVMnZJaE14djBjdHh3NHVWa0d1cUhnRW9xWWYtQW01dkpLNWs2dE1BSVI1RDAzdlBpLWtGY2w1YlZUNnVEOWJSQmVDVnZXdzZac2lydjdmMEdPcXpiTFljWDZVSEtOYXhPV3AzMWxnRndEbjFQSG1zWmZJaHB2VWJCd2tocl9VZFNFdDdRRjAzNFdsRkV6TVRTU0dpZ2tCTlltNTZ6QVpfY2VSaS05TG91ZUUtaS1aLTNBTC1vSzVfbXNmOTl3UkdsZ2pWb2kxYy1PUTZQY1hPOEZERHNTVEw0V25vdzdJUlByRnRhTnRBcDBEX1RQNmgwSGpqYnFjdU44eU40MEhvU1NXbl9HOXJTLVozRzM3cURsZkdEcXZyOWROaFB3d0twN1BHNDZNZWF4bkI5dGYxSEZ1S2h3dkk2MEZLMC1kVFU4b2pqRDQ3SS1teXZMaDJITy1hTU9PMS1iQTA3dkltT2pobWh4RS1uSXdzQVk5TURvQ1hFQTZYeE10ckdFQkNOVFVaQ1hTSW4wYmpUYTVKNlhXLUl6XzdLU2dhclRla045N1RMVzdFdEpUNXFwekhYajViZld6c2JKMWF5bWVDczFybFkyZjBLSnpLXzk3QmlCazlMVjdwU0VfX2JOcTFpa1hvZlVRZkFKekhaWDF0M0xRUzhTUVRhRzBsX192em9pb05NeWhTZ0tUR0dGenpabE5jdmRCRTJEeURLZ2VkZ09lZDJsd0MxTjg3bFY5U1pSNUdPbTV3ZjY5MmVMYWN6cEZFMGUzUHg4T002TlM0UkQ5QXgzNDhZcU9RS2tlTElFRjR1UnVzdTlGUjhDZ1Z4dXBlckxabFdaaWZIVkdudDBrdWdqSy0zU3FwZVg0ZlVlbS04LVZBY2V6LVNpbVc4Q3JybnlCSDdWNUlqamk1MEZUNjFFVUh4aE5JeFQtendlLTRlUkRvWUVhN3hwdUs2MDNZTlFUSU84a25QeHZaUE4ybjJoZkp3ckRhbkxkaVhLSUxWUjdONE9RcGYxd0RUbFplNXJuTzJQbVBNSk1zZHZvUDZ6ellYakN6SmtqQ3kyVTBnb0pYQ0hrWi00Rm9FZkpnQWJLNGlEY0lLWGIzTGZRSE9SQUFfTmx4THU0bmFpdElaUVdfNktqR0g1eXV6ZXFKdnVqTHVqam4tUXU5MGthOU5JaS11QUxxcU1QYTJ6S0padnJlNkJBTU5QTU1PODFFckRNc3lNTDZ6cl8zSWl1ZDE1dWtaRjVDQkpmSXZWeklxSldwanJiYTRldGF5S0dLUGZSc1k4dUpNU0daUTZSa0dra1RrWU1scnhlaG9tM2JzcG5QSkJqMVUtaU1paDlKVEE3d1JMa0RsVHEta2lKbDZQYkVielVsQ2RvRFNLUkxiR0RlLXEtM0xWeV9Wa1E0OXlrUy05Tm5GRF9PTDVFNlYxSG9JMW10dVI4SmprOWlsbWxmR25NVHp3emY1UGI0Wkp2Tjc2WWRZZTE0dnoxbWtPZFZnVTVicm9PNl9aY0NWQ3E0MUxVZVhmZTJDZlN3SGJfM0x2aW5ZMWZMMVJ4UFhIZzlwendIX0lINXFjRWFybUdQcFpVNUFKenN6dVNXQ0k2QlNEdmNpbnptN3pNNjNoUlFxNG42Z1U4NERJWjRhMW1pQXJyN05rbjlDVnVRdDJEWHVUcWV1N1l2UkNtUk1qNVFYenVEeHJzdGpfaHlIdHJmUDFNdmlxWnFMckUzbzZCRm1vZlVLNGpWUEJzZC1YaHMyU0J5dlFjVjlCQ0RjRGU5X0c3bWpobTVRelprMEZxM0l2d3BpWWI2bkc5Uk9vZzZRQnJQVjFwUExjc09TMm42WWoyX0RNLXFBSzNLdVVMamdXRlNMNUVyRzhwUldHT3ZWU0p2X1c3VXBLVHI5WXZGUVEzbWZyN0REYTVER0tRbzlfblRyVDUySkh0bTNSVUE2bzlqNy1hUUVidnBNUEx6eDRGSThGWWRtejQzMXlpMDhEbVMwVkU5SVYtRzhoOXpJY19yZjZsWmU1dS15bnVZNnJQUV95bGFYSGIydHpLb0Fjdkxpckw1RVJBZFNBNk96bEU1SVdZcTNhU2JPTUdxN0dCc3d2LUR2UUxlRGtBSWhQbHFyTGpfX1pxTHkxRTVrOEt6cllnZXBjdUc4M0I0SEFfWGp5RXRPMDQ3cEZma0ptektBckdOU2V6NFN4cENIaWdyNkZIMHpaVWYxdVNFNVFPeGliUVVJa0ZQV2FQcUUyOVV4STEyQ1JETUtQLVlpZjlWQTd0NjB2XzNPS05XUVlHZl9UcGRKM3pEaC1xR0NOZFlxa3d0eTU0TUtvU3BLNzZ0X1lEZ2NCcm9SNm15NmZtbVRUei1lSDVpS0ZvSnFxYWtoX0o5dGhrU1l1alNzYm1zckcwMmZidmNMMW5uOURRSUdVMlJLRGcwWWI1cVVoek1nTHBraWNjbXZ0SGlrQVFmSW9nZWVmNTMwTnhpb3VBOTNYSEd0dTVfcHVYVGp3REJzZjhPdGQ0eE1LMWZoYnFvZXpGYzVJRklxWWt5WXlBMXFCN1E3VmVwc1djSHF1cEVtOHhjendOYnhyeXh4dmpHaUJwbjllbW9HejdQc1RuWmpGRnVRNzZxcEhqblRqUENWRnFHdEp0N3pEZnBkcGZ0cVBYVk1sQldzT3NOY2pYVFQ4eUJWa0ZSbVN2akNzeE55OHZuZGFEZWh5dno2Z3ZRbXBzNWRGekhNNmNHOWw4M2ZSNWxwTEc1a0JQbXlkQkNiRno2NHhsX2VGVW0yUFVtTWI2Q1ZSaHBmNXJjZmxHWGRRWDlDLVBGSjFVSG1pc1JWMG1VM2xnekJuTWpDSzg4MFctNGZodldLTFZ4OW5KbEJObTdtWF9ZeGVMQUg2Vjh6VFA0YTVRaEVIUGVvVFJ4WlEyQTM4emEzcG5iSS1YaXZsOTE4eHBGVUN3UExpbGRjU2ZGQU5xNDk5OU43X1lUVmd4bXRPTi00YXJkWWZwbE9yRnMwbzRzbEhYVVNOUUtNQUFLNXQzbGFtcDVNaFZRUWExWFAwVk5SdV9KS0lZYUJCV1V5T01hd25vZDA2MEY5T2N2Qkw3ZVUtQlJkak5NeVgyMFRMT3NkWUQ5SXR6STd6RVFIX3l5R1FEdVFfall0X2ttTURTMl9FTFd6TVhDSUs4Ni10bGFCT293dnZGNlVpWlRDM2N5WG9Ic0ZoNzFjRC1MWks1VXg3ZFhRNmR4bUtGRDE5SG9uNlQtV0FySk5lOEk1NGpUaHU2SWZqX3M4VDhMQVY3QU5jMl9INGZzeHFuLXg5MjJ3T21BamQzNmYzMF9wMmc2VWdlQWZNVkFFc3czWG1uUjhhbGFLOTVkMnJKSF9fNzVOSDNIUjl1RXBaNV9sLWRhNmVDOGdvUDBLbTk1UU4zeEtVSWxjb0Z2d3AwTTh4QUZMelN0RUVSN0l5dDNGRnNBalVuNzFIWlVIM3dEVHpNUW5Bdnp5RWdsNmtkRnp0dG1PQ1NOclBGTXJpQ3FCejEtRXpYT2YxR0d2NXJ0N05aUW9VX0hjM2xUN2N5eWNUSV9JNDJQUjdmRG5RUHAyLXZJRXNOQXoxNF9zXzZxUktVVldoTEJWWUVnOU1NMXR3bkNkM0d5MjBKMmdvZ2xxbzdRa3JXYXp5MzNBdVdXMV82RWxOZHQ0OS0xYWNpTHNCQUZxaDdxcUM4eDM3bGR1MFhRTzczU0pYMDV1dzRSTk5fUHFXOGY1cnJFblhuWTV4cl84d1gwOF9EOHlXNjc1NkxFT2Q3Qk9XYmRCbHl2a2lhTG5naDN5UDlPQVQ1ZjV5cHlhc0l1S3lPckFnX1JqQUtPZzNUQm1UZjJZTGs1OUNxWFpnbWhKNHJrS1M5X2p3UXI1c2lXZnNFclVXbVFRaExueGZZNGVXMVR6NDdRNnEtSThJdmJHdm9NZEc4aGRESXhzczJTQmZLZ0lpZlB6NGpuLXp2TVVzOEZnTDA4UWpDVEFheHFHOFJYbVNqSW5aRERESHlzY3owSmRabHpITDJsZkNMMVk1eE9Ha2JrTV9YZjBreER4ODF3bVFjWTNXbjl0eFlFLXhFVDloNndOTGN6UVh6TzlONXFpSDZpNGJadlR0MkxqdmJrV1M5MEw0TFVFZ2puSE9PTVQtWmtYbWI2d3FoVzlfbjNvWkRZcjlNQ0xPWU5FQ3BUVzF0aGZtTVRDTkk5dW95V1hHZDdDVXAxaUo2UGRDT3lXb19vblpnaGVQY0JNUkhmdU9NN3pfRnJIdTlqNEN2Yk5nd1A2cnFvQnNWcFJMRUp6bHhZbU0xZFF1WnFheVlYbXBiOS1XTnhtZ01tWVd3TVhaZFZWWXJhOTJmSXJUVzdkU0lkeVpZQWI0YUtIZVlMdExJTEtkRjZHLWZiNmVVZTU1Vm9sMEl5S2I1X0xqTFhRLUtBUW92OC1RaXhUODN4N3k3cFZDeEZ4R3R3ZnBOLTFVaklfTDVEZHQ3N1dxMi1wN3JaNDFoOE9ER18yUmlOVVFucUFEeDc0WVlFX2I1SlA4MjY2SDhCSEQxVjFWUFR0aVFvUFNETF9kZTBFVnZ3dUt4Y1pRYnV1UnpzRUpXZHZfWkVJRks0dXdfaHdQOEV4ZGFpb2M5a1FzMmdJem9DVW1yaGFjSkZUU2J6YXI5elBteld2Vmc0a3ZjVUMwSVkzTTMxS1JhT0plWkkxQ2FWWmlfblZHLUYtbmFJQ3hIcm9tQUVBZDBEVFJJOTNBU1E3QUlIVE10bDlfQkxOaEo0UU1RQ0dSblFXdkF0SzhvbnA2NGVzcXBubzFfODktWDFzTVBaejlOMWVOOWR5UHBfZlVfOEpUdmwtVF9lRGwtOXJaS3duUnFOdHhHeEZuVXBNdXE0MEg5dThlMHhYbXJlOGdTTzhxZEtKeFpvMkNEUE9Ca2RGcF94Rk9GNFNCUjdQWGx4aHl3SWJ4dVYyRlYtd2FERzR6UDZCa1ZKdlZHejJSNkpzTXVELUoxdWJtc2hhQTJsR19fbXItLXVUbFlybGxLanFVUHBQS1BGUDZVZU9weFVWdV9CWlZBYnBJRmV4VVdxc3hrZXdxaGlFS0lSckF5Lk9YUTNscWkxdk4wNG1sb1Mxa2xPdVE"}' headers: Accept: - application/json @@ -170,7 +223,7 @@ interactions: uri: https://vault96041739.vault.azure.net/keys/restore?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/658aa7c54d564c7aa20e529f12a9894a","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"nRkoF6WTkMhvxLuxkozbNkz4uqgpJyCWMU8DkIme7oop9qyVQy1AejXbY0_OGjn4RpN-TRhJ7NxoFBb3Znq0FouSjp_a9GrUwVIfNF5kJ9yJSWnn_SpIB1ZwFxph8O3mq5YW84L6WQ3JZvjhF-IqDYmfEzjwlVrnBjIe-Mk6kT0BcXvaJb-g_EkbcmSrm09SKul_hQB7Ga_yPT-gj6Yupb1swEnDhEWcchTcAnkEwxiQSVbuFxE0nIv0U5T5x41n4NEGeuezOUjKA4hrx5ghROBK968DFAqjEpdfrYEBOgqn3urN5GnSnh6S3LmMRKMnGdSOhAhCY4HUC9XqsXibKQ","e":"AQAB"},"attributes":{"enabled":true,"created":1562686804,"updated":1562686804,"recoveryLevel":"Purgeable"}}' + string: '{"key":{"kid":"https://vault96041739.vault.azure.net/keys/test-key/7089b08b10a64ec68db241e20e2109d0","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"qbGcivUVxthKiCxcjxOGdsiItrjjTm0_-o0gkZmRSDlEY4ixbjJYBZMwZLxLm7_ZzoGTVTdON-_6bLu-ZwFqAAe2Nc_HDLkL3FTn1YkjTSA5I6FTXI-CC79m3mPLbrVNkHRUkN7QPpl9Qak8RyRl4TA5XtOp_ztKm5Lfuc1eJsosaRdqaMe2UlEbu0XCdYDoP1UoNRAhrUZwli-GHH6yWQxq0SBtnfUsXH0CkgprrnpDYM3dxUkjJJ6IRpW4acRXrFDg-ccCBQDRLty6eSRehugfZyTGciaT4fBcj245OjytQZHxz2ZGixRxN0YsvjoZXqquXKqfkKEcknMDNapyFw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703512,"updated":1562703512,"recoveryLevel":"Purgeable"}}' headers: cache-control: - no-cache @@ -179,7 +232,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:40:04 GMT + - Tue, 09 Jul 2019 20:18:32 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_recover.yaml b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_recover.yaml index 8be5e74d092f..d1318da0dda6 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_recover.yaml +++ b/sdk/keyvault/azure-keyvault-keys/tests/recordings/test_samples_keys_async.test_example_keys_recover.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"kty": "RSA"}' + body: null headers: Accept: - application/json @@ -9,7 +9,7 @@ interactions: Connection: - keep-alive Content-Length: - - '14' + - '0' Content-Type: - application/json; charset=utf-8 User-Agent: @@ -18,16 +18,14 @@ interactions: uri: https://vaultfcf91456.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/9d82564a217e42d2a567921b62670a43","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tNjZECUWLz5zzJ6mHYVC2rhNqAwooRR_RTZwE6E3w-vmYRlLf-ZLlWh4U7-djujJ5ce6HowOmzXKDxy_EM-EpsbMtv6EPscsyNYl-hwQHJ4lKmsXY5YbQgjFo2xB1xUNnAYLN6QKFbXiKpWxha22o8JzjtCIs5WTq5Tc8g9YQVx7loJ4p2JB7iG3LInz9l__u4l-LBroF4FRiM6H128ABVBKDGayGRPQo1E_gIp8nW83KlUOk34GzhiFQNc8yiV4ioU2t3T1p71Hi6IPhAk6SMsFa5N7xEfZhU9it7wH9syQ4JpOtHSGuO-bXCAhxUciVbmaevx347BeRfeBgg0SDw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686868,"updated":1562686868,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '' headers: cache-control: - no-cache content-length: - - '656' - content-type: - - application/json; charset=utf-8 + - '0' date: - - Tue, 09 Jul 2019 15:41:08 GMT + - Tue, 09 Jul 2019 20:18:53 GMT expires: - '-1' pragma: @@ -36,6 +34,9 @@ interactions: - Microsoft-IIS/10.0 strict-transport-security: - max-age=31536000;includeSubDomains + www-authenticate: + - Bearer authorization="https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47", + resource="https://vault.azure.net" x-aspnet-version: - 4.0.30319 x-content-type-options: @@ -49,10 +50,10 @@ interactions: x-powered-by: - ASP.NET status: - code: 200 - message: OK + code: 401 + message: Unauthorized - request: - body: null + body: '{"kty": "RSA"}' headers: Accept: - application/json @@ -61,23 +62,25 @@ interactions: Connection: - keep-alive Content-Length: - - '0' + - '14' + Content-Type: + - application/json; charset=utf-8 User-Agent: - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 - method: DELETE - uri: https://vaultfcf91456.vault.azure.net/keys/key-name?api-version=7.0 + method: POST + uri: https://vaultfcf91456.vault.azure.net/keys/key-name/create?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562686868,"scheduledPurgeDate":1570462868,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/9d82564a217e42d2a567921b62670a43","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tNjZECUWLz5zzJ6mHYVC2rhNqAwooRR_RTZwE6E3w-vmYRlLf-ZLlWh4U7-djujJ5ce6HowOmzXKDxy_EM-EpsbMtv6EPscsyNYl-hwQHJ4lKmsXY5YbQgjFo2xB1xUNnAYLN6QKFbXiKpWxha22o8JzjtCIs5WTq5Tc8g9YQVx7loJ4p2JB7iG3LInz9l__u4l-LBroF4FRiM6H128ABVBKDGayGRPQo1E_gIp8nW83KlUOk34GzhiFQNc8yiV4ioU2t3T1p71Hi6IPhAk6SMsFa5N7xEfZhU9it7wH9syQ4JpOtHSGuO-bXCAhxUciVbmaevx347BeRfeBgg0SDw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686868,"updated":1562686868,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/c062f7d6222743fc8ce103b1d9850298","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wTcadP8ONiTwlvzh9KqAqaScUg1734Ux7ykEs-X3wyoi7MjSFaGb-ZAhDrH7iKSbArQeT_Pp4EvCl2axAvOJvT_P2Kw9CE9rtOaD2p6lMZjqY2R0jiMQVyYHpvOICi-87uhEe_N3pdoWxS93WXjo8NblF2dmvwbwCmuAD4IPWD5Ve0VGf2kKh9QtGmJHrQ2C1tdkPP1VHQmfgLNjBQmvPPxK1aAUgTf1jw1NxNErOyHOyiKuYe_phcO8qg0st7Q3wRuDfRYs2_J_1veYQGytghxlAlZ8DjiyUyKNK2fTDUKU5-zDCFWnn9mI1eiTVKXo_4FgEWLfno9vhZEvsO4EOw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703534,"updated":1562703534,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '787' + - '656' content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:08 GMT + - Tue, 09 Jul 2019 20:18:53 GMT expires: - '-1' pragma: @@ -110,166 +113,24 @@ interactions: - gzip, deflate Connection: - keep-alive + Content-Length: + - '0' User-Agent: - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' - headers: - cache-control: - - no-cache - content-length: - - '76' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 09 Jul 2019 15:41:09 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.160.58;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.872 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' - headers: - cache-control: - - no-cache - content-length: - - '76' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 09 Jul 2019 15:41:11 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.160.58;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.872 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 - response: - body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' - headers: - cache-control: - - no-cache - content-length: - - '76' - content-type: - - application/json; charset=utf-8 - date: - - Tue, 09 Jul 2019 15:41:14 GMT - expires: - - '-1' - pragma: - - no-cache - server: - - Microsoft-IIS/10.0 - strict-transport-security: - - max-age=31536000;includeSubDomains - x-aspnet-version: - - 4.0.30319 - x-content-type-options: - - nosniff - x-ms-keyvault-network-info: - - addr=131.107.160.58;act_addr_fam=InterNetwork; - x-ms-keyvault-region: - - westus - x-ms-keyvault-service-version: - - 1.1.0.872 - x-powered-by: - - ASP.NET - status: - code: 404 - message: Not Found -- request: - body: null - headers: - Accept: - - application/json - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - python/3.5.4 (Windows-10-10.0.18362-SP0) azure-core/1.0.0b1 azsdk-python-azure-keyvault/7.0 - method: GET - uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 + method: DELETE + uri: https://vaultfcf91456.vault.azure.net/keys/key-name?api-version=7.0 response: body: - string: '{"error":{"code":"KeyNotFound","message":"Deleted Key not found: key-name"}}' + string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562703534,"scheduledPurgeDate":1570479534,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/c062f7d6222743fc8ce103b1d9850298","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wTcadP8ONiTwlvzh9KqAqaScUg1734Ux7ykEs-X3wyoi7MjSFaGb-ZAhDrH7iKSbArQeT_Pp4EvCl2axAvOJvT_P2Kw9CE9rtOaD2p6lMZjqY2R0jiMQVyYHpvOICi-87uhEe_N3pdoWxS93WXjo8NblF2dmvwbwCmuAD4IPWD5Ve0VGf2kKh9QtGmJHrQ2C1tdkPP1VHQmfgLNjBQmvPPxK1aAUgTf1jw1NxNErOyHOyiKuYe_phcO8qg0st7Q3wRuDfRYs2_J_1veYQGytghxlAlZ8DjiyUyKNK2fTDUKU5-zDCFWnn9mI1eiTVKXo_4FgEWLfno9vhZEvsO4EOw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703534,"updated":1562703534,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache content-length: - - '76' + - '787' content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:17 GMT + - Tue, 09 Jul 2019 20:18:53 GMT expires: - '-1' pragma: @@ -291,8 +152,8 @@ interactions: x-powered-by: - ASP.NET status: - code: 404 - message: Not Found + code: 200 + message: OK - request: body: null headers: @@ -317,7 +178,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:20 GMT + - Tue, 09 Jul 2019 20:18:53 GMT expires: - '-1' pragma: @@ -365,7 +226,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:23 GMT + - Tue, 09 Jul 2019 20:18:56 GMT expires: - '-1' pragma: @@ -413,7 +274,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:26 GMT + - Tue, 09 Jul 2019 20:19:00 GMT expires: - '-1' pragma: @@ -452,7 +313,7 @@ interactions: uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562686868,"scheduledPurgeDate":1570462868,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/9d82564a217e42d2a567921b62670a43","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tNjZECUWLz5zzJ6mHYVC2rhNqAwooRR_RTZwE6E3w-vmYRlLf-ZLlWh4U7-djujJ5ce6HowOmzXKDxy_EM-EpsbMtv6EPscsyNYl-hwQHJ4lKmsXY5YbQgjFo2xB1xUNnAYLN6QKFbXiKpWxha22o8JzjtCIs5WTq5Tc8g9YQVx7loJ4p2JB7iG3LInz9l__u4l-LBroF4FRiM6H128ABVBKDGayGRPQo1E_gIp8nW83KlUOk34GzhiFQNc8yiV4ioU2t3T1p71Hi6IPhAk6SMsFa5N7xEfZhU9it7wH9syQ4JpOtHSGuO-bXCAhxUciVbmaevx347BeRfeBgg0SDw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686868,"updated":1562686868,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562703534,"scheduledPurgeDate":1570479534,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/c062f7d6222743fc8ce103b1d9850298","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wTcadP8ONiTwlvzh9KqAqaScUg1734Ux7ykEs-X3wyoi7MjSFaGb-ZAhDrH7iKSbArQeT_Pp4EvCl2axAvOJvT_P2Kw9CE9rtOaD2p6lMZjqY2R0jiMQVyYHpvOICi-87uhEe_N3pdoWxS93WXjo8NblF2dmvwbwCmuAD4IPWD5Ve0VGf2kKh9QtGmJHrQ2C1tdkPP1VHQmfgLNjBQmvPPxK1aAUgTf1jw1NxNErOyHOyiKuYe_phcO8qg0st7Q3wRuDfRYs2_J_1veYQGytghxlAlZ8DjiyUyKNK2fTDUKU5-zDCFWnn9mI1eiTVKXo_4FgEWLfno9vhZEvsO4EOw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703534,"updated":1562703534,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -461,7 +322,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:29 GMT + - Tue, 09 Jul 2019 20:19:03 GMT expires: - '-1' pragma: @@ -500,7 +361,7 @@ interactions: uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name?api-version=7.0 response: body: - string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562686868,"scheduledPurgeDate":1570462868,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/9d82564a217e42d2a567921b62670a43","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tNjZECUWLz5zzJ6mHYVC2rhNqAwooRR_RTZwE6E3w-vmYRlLf-ZLlWh4U7-djujJ5ce6HowOmzXKDxy_EM-EpsbMtv6EPscsyNYl-hwQHJ4lKmsXY5YbQgjFo2xB1xUNnAYLN6QKFbXiKpWxha22o8JzjtCIs5WTq5Tc8g9YQVx7loJ4p2JB7iG3LInz9l__u4l-LBroF4FRiM6H128ABVBKDGayGRPQo1E_gIp8nW83KlUOk34GzhiFQNc8yiV4ioU2t3T1p71Hi6IPhAk6SMsFa5N7xEfZhU9it7wH9syQ4JpOtHSGuO-bXCAhxUciVbmaevx347BeRfeBgg0SDw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686868,"updated":1562686868,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"recoveryId":"https://vaultfcf91456.vault.azure.net/deletedkeys/key-name","deletedDate":1562703534,"scheduledPurgeDate":1570479534,"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/c062f7d6222743fc8ce103b1d9850298","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wTcadP8ONiTwlvzh9KqAqaScUg1734Ux7ykEs-X3wyoi7MjSFaGb-ZAhDrH7iKSbArQeT_Pp4EvCl2axAvOJvT_P2Kw9CE9rtOaD2p6lMZjqY2R0jiMQVyYHpvOICi-87uhEe_N3pdoWxS93WXjo8NblF2dmvwbwCmuAD4IPWD5Ve0VGf2kKh9QtGmJHrQ2C1tdkPP1VHQmfgLNjBQmvPPxK1aAUgTf1jw1NxNErOyHOyiKuYe_phcO8qg0st7Q3wRuDfRYs2_J_1veYQGytghxlAlZ8DjiyUyKNK2fTDUKU5-zDCFWnn9mI1eiTVKXo_4FgEWLfno9vhZEvsO4EOw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703534,"updated":1562703534,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -509,7 +370,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:29 GMT + - Tue, 09 Jul 2019 20:19:03 GMT expires: - '-1' pragma: @@ -550,7 +411,7 @@ interactions: uri: https://vaultfcf91456.vault.azure.net/deletedkeys/key-name/recover?api-version=7.0 response: body: - string: '{"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/9d82564a217e42d2a567921b62670a43","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"tNjZECUWLz5zzJ6mHYVC2rhNqAwooRR_RTZwE6E3w-vmYRlLf-ZLlWh4U7-djujJ5ce6HowOmzXKDxy_EM-EpsbMtv6EPscsyNYl-hwQHJ4lKmsXY5YbQgjFo2xB1xUNnAYLN6QKFbXiKpWxha22o8JzjtCIs5WTq5Tc8g9YQVx7loJ4p2JB7iG3LInz9l__u4l-LBroF4FRiM6H128ABVBKDGayGRPQo1E_gIp8nW83KlUOk34GzhiFQNc8yiV4ioU2t3T1p71Hi6IPhAk6SMsFa5N7xEfZhU9it7wH9syQ4JpOtHSGuO-bXCAhxUciVbmaevx347BeRfeBgg0SDw","e":"AQAB"},"attributes":{"enabled":true,"created":1562686868,"updated":1562686868,"recoveryLevel":"Recoverable+Purgeable"}}' + string: '{"key":{"kid":"https://vaultfcf91456.vault.azure.net/keys/key-name/c062f7d6222743fc8ce103b1d9850298","kty":"RSA","key_ops":["encrypt","decrypt","sign","verify","wrapKey","unwrapKey"],"n":"wTcadP8ONiTwlvzh9KqAqaScUg1734Ux7ykEs-X3wyoi7MjSFaGb-ZAhDrH7iKSbArQeT_Pp4EvCl2axAvOJvT_P2Kw9CE9rtOaD2p6lMZjqY2R0jiMQVyYHpvOICi-87uhEe_N3pdoWxS93WXjo8NblF2dmvwbwCmuAD4IPWD5Ve0VGf2kKh9QtGmJHrQ2C1tdkPP1VHQmfgLNjBQmvPPxK1aAUgTf1jw1NxNErOyHOyiKuYe_phcO8qg0st7Q3wRuDfRYs2_J_1veYQGytghxlAlZ8DjiyUyKNK2fTDUKU5-zDCFWnn9mI1eiTVKXo_4FgEWLfno9vhZEvsO4EOw","e":"AQAB"},"attributes":{"enabled":true,"created":1562703534,"updated":1562703534,"recoveryLevel":"Recoverable+Purgeable"}}' headers: cache-control: - no-cache @@ -559,7 +420,7 @@ interactions: content-type: - application/json; charset=utf-8 date: - - Tue, 09 Jul 2019 15:41:30 GMT + - Tue, 09 Jul 2019 20:19:03 GMT expires: - '-1' pragma: diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py new file mode 100644 index 000000000000..04ead0b933b1 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -0,0 +1,146 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +""" +Tests for the HTTP challenge authentication implementation. These tests aren't parallelizable, because +the challenge cache is global to the process. +""" + +try: + from unittest.mock import Mock +except ImportError: # python < 3.3 + from mock import Mock + +from azure.core.credentials import AccessToken +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import HttpRequest +from azure.keyvault.keys._shared import ChallengeAuthPolicy, HttpChallenge, HttpChallengeCache +import pytest + +from helpers import mock_response, Request, validating_transport + + +def test_challenge_cache(): + # ensure the test starts with an empty cache + HttpChallengeCache.clear() + + url_a = "https://azure.service.a" + challenge_a = HttpChallenge(url_a, "Bearer authorization=authority A, resource=resource A") + + url_b = "https://azure.service.b" + challenge_b = HttpChallenge(url_b, "Bearer authorization=authority B, resource=resource B") + + for url, challenge in zip((url_a, url_b), (challenge_a, challenge_b)): + HttpChallengeCache.set_challenge_for_url(url, challenge) + assert HttpChallengeCache.get_challenge_for_url(url) == challenge + assert HttpChallengeCache.get_challenge_for_url(url + "/some/path") == challenge + assert HttpChallengeCache.get_challenge_for_url(url + "/some/path?with-query=string") == challenge + assert HttpChallengeCache.get_challenge_for_url(url + ":443") == challenge + + HttpChallengeCache.remove_challenge_for_url(url) + assert not HttpChallengeCache.get_challenge_for_url(url) + + +def test_challenge_parsing(): + authority = "https://login.authority.net/tenant" + resource = "https://challenge.resource" + challenge = HttpChallenge( + "https://request.uri", challenge="Bearer authorization={}, resource={}".format(authority, resource) + ) + + assert challenge.get_authorization_server() == authority + assert challenge.get_resource() == resource + + +def test_policy(): + # ensure the test starts with an empty cache + HttpChallengeCache.clear() + + expected_scope = "https://challenge.resource/.default" + expected_token = "expected_token" + challenge = Mock( + status_code=401, + headers={ + "WWW-Authenticate": 'Bearer authorization="https://login.authority.net/tenant", resource={}'.format( + expected_scope + ) + }, + ) + success = Mock(status_code=200) + data = {"spam": "eggs"} + + responses = (r for r in (challenge, success)) + + def send(request): + response = next(responses) + if response is challenge: + # this is the first request + assert not request.body + assert request.headers["Content-Length"] == "0" + elif response is success: + # this is the second request + assert request.body == data + assert expected_token in request.headers["Authorization"] + return response + + def get_token(*scopes): + assert len(scopes) is 1 + assert scopes[0] == expected_scope + return AccessToken(expected_token, 0) + + credential = Mock(get_token=Mock(wraps=get_token)) + pipeline = Pipeline(policies=[ChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + pipeline.run(HttpRequest("POST", "https://azure.service", data=data)) + + assert credential.get_token.call_count == 1 + + +def test_policy_updates_cache(): + """ + It's possible for the challenge returned for a request to change, e.g. when a vault is moved to a new tenant. + When the policy receives a 401, it should update the cached challenge for the requested URL, if one exists. + """ + + # ensure the test starts with an empty cache + HttpChallengeCache.clear() + + url = "https://azure.service/path" + first_scope = "https://first-scope" + first_token = "first-scope-token" + second_scope = "https://second-scope" + second_token = "second-scope-token" + challenge_fmt = 'Bearer authorization="https://login.authority.net/tenant", resource={}' + + # mocking a tenant change: + # 1. first request -> respond with challenge + # 2. second request should be authorized according to the challenge -> respond with success + # 3. third request should match the second -> respond with a new challenge + # 4. fourth request should be authorized according to the new challenge -> respond with success + # 5. fifth request should match the fourth -> respond with success + transport = validating_transport( + requests=( + Request(url), + Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}), + ), + responses=( + mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(first_scope)}), + mock_response(status_code=200), + mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(second_scope)}), + mock_response(status_code=200), + mock_response(status_code=200), + ), + ) + + tokens = (t for t in [first_token] * 2 + [second_token] * 2) + credential = Mock(get_token=lambda _: AccessToken(next(tokens), 0)) + pipeline = Pipeline(policies=[ChallengeAuthPolicy(credential=credential)], transport=transport) + + # policy should complete and cache the first challenge + pipeline.run(HttpRequest("GET", url)) + + # The next request will receive a challenge. The policy should handle it and update the cache entry. + pipeline.run(HttpRequest("GET", url)) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py new file mode 100644 index 000000000000..c4d72b1c7550 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -0,0 +1,116 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +""" +Tests for the HTTP challenge authentication implementation. These tests aren't parallelizable, because +the challenge cache is global to the process. +""" +import asyncio + +try: + from unittest.mock import Mock +except ImportError: # python < 3.3 + from mock import Mock + +from azure.core.credentials import AccessToken +from azure.core.pipeline import AsyncPipeline +from azure.core.pipeline.transport import HttpRequest +from azure.keyvault.keys._shared import AsyncChallengeAuthPolicy, HttpChallenge, HttpChallengeCache +import pytest + +from helpers import async_validating_transport, mock_response, Request + + +@pytest.mark.asyncio +async def test_policy(): + # ensure the test starts with an empty cache + HttpChallengeCache.clear() + + expected_scope = "https://challenge.resource/.default" + expected_token = "expected_token" + challenge = Mock( + status_code=401, + headers={ + "WWW-Authenticate": 'Bearer authorization="https://login.authority.net/tenant", resource={}'.format( + expected_scope + ) + }, + ) + success = Mock(status_code=200) + data = {"spam": "eggs"} + + responses = (r for r in (challenge, success)) + + async def send(request): + response = next(responses) + if response is challenge: + # this is the first request + assert not request.body + assert request.headers["Content-Length"] == "0" + elif response is success: + # this is the second request + assert request.body == data + assert expected_token in request.headers["Authorization"] + return response + + async def get_token(*scopes): + print("get token") + assert len(scopes) is 1 + assert scopes[0] == expected_scope + return AccessToken(expected_token, 0) + + credential = Mock(get_token=get_token) + pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=Mock(send=send)) + await pipeline.run(HttpRequest("POST", "https://azure.service", data=data)) + + +@pytest.mark.asyncio +async def test_policy_updates_cache(): + """ + It's possible for the challenge returned for a request to change, e.g. when a vault is moved to a new tenant. + When the policy receives a 401, it should update the cached challenge for the requested URL, if one exists. + """ + + # ensure the test starts with an empty cache + HttpChallengeCache.clear() + + url = "https://azure.service/path" + first_scope = "https://first-scope" + first_token = "first-scope-token" + second_scope = "https://second-scope" + second_token = "second-scope-token" + challenge_fmt = 'Bearer authorization="https://login.authority.net/tenant", resource={}' + + # mocking a tenant change: + # 1. first request -> respond with challenge + # 2. second request should be authorized according to the challenge -> respond with success + # 3. third request should match the second -> respond with a new challenge + # 4. fourth request should be authorized according to the new challenge -> respond with success + # 5. fifth request should match the fourth -> respond with success + transport = async_validating_transport( + requests=( + Request(url), + Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(first_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}), + Request(url, required_headers={"Authorization": "Bearer {}".format(second_token)}), + ), + responses=( + mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(first_scope)}), + mock_response(status_code=200), + mock_response(status_code=401, headers={"WWW-Authenticate": challenge_fmt.format(second_scope)}), + mock_response(status_code=200), + mock_response(status_code=200), + ), + ) + + tokens = (t for t in [first_token] * 2 + [second_token] * 2) + credential = Mock(get_token=asyncio.coroutine(lambda _: AccessToken(next(tokens), 0))) + pipeline = AsyncPipeline(policies=[AsyncChallengeAuthPolicy(credential=credential)], transport=transport) + + # policy should complete and cache the first challenge + await pipeline.run(HttpRequest("GET", url)) + + # The next request will receive a challenge. The policy should handle it and update the cache entry. + await pipeline.run(HttpRequest("GET", url)) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_key_client.py b/sdk/keyvault/azure-keyvault-keys/tests/test_key_client.py index b2b29fb1b737..0f736d0e5bcd 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_key_client.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_key_client.py @@ -8,7 +8,7 @@ import time from azure.core.exceptions import ResourceNotFoundError -from azure.keyvault.keys._generated.v7_0.models import JsonWebKey +from azure.keyvault.keys._shared._generated.v7_0.models import JsonWebKey from keys_preparer import VaultClientPreparer from keys_test_case import KeyVaultTestCase from devtools_testutils import ResourceGroupPreparer diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_keys_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_keys_async.py index dc7e701f1e13..0d3d4894c487 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_keys_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_keys_async.py @@ -11,7 +11,7 @@ from keys_async_preparer import AsyncVaultClientPreparer from keys_async_test_case import AsyncKeyVaultTestCase -from azure.keyvault.keys._generated.v7_0.models import JsonWebKey +from azure.keyvault.keys._shared._generated.v7_0.models import JsonWebKey from dateutil import parser as date_parse diff --git a/sdk/keyvault/azure-keyvault-secrets/README.md b/sdk/keyvault/azure-keyvault-secrets/README.md index 41dc667966e1..ab50a297d194 100644 --- a/sdk/keyvault/azure-keyvault-secrets/README.md +++ b/sdk/keyvault/azure-keyvault-secrets/README.md @@ -122,7 +122,6 @@ The following section provides several code snippets using the above created `se updated_secret = secret_client.update_secret("secret-name", content_type=content_type, tags=tags) print(updated_secret.name) - print(updated_secret.value) print(updated_secret.version) print(updated_secret.updated) print(updated_secret.content_type) @@ -133,7 +132,7 @@ The following section provides several code snippets using the above created `se ### Delete a Secret `delete_secret` deletes a secret previously stored in the Key Vault. When [soft-delete][soft_delete] is not enabled for the Key Vault, this operation permanently deletes the secret. ```python - secret = secret_client.delete_secret("secret-name") + deleted_secret = secret_client.delete_secret("secret-name") print(deleted_secret.name) print(deleted_secret.deleted_date) diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/__init__.py index 34de167e8c94..44967833f2df 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/__init__.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/__init__.py @@ -3,6 +3,5 @@ # Licensed under the MIT License. # ------------------------------------ from ._client import SecretClient -from ._models import Secret, SecretAttributes, DeletedSecret __all__ = ["SecretClient"] diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_client.py index b31ff0047445..f15b6779b9d3 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_client.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_client.py @@ -2,16 +2,23 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from typing import Any, Dict, Generator, Mapping, Optional from datetime import datetime +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Dict, Generator, Mapping, Optional + from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError -from ._internal import _KeyVaultClientBase +from ._shared import KeyVaultClientBase from ._models import Secret, DeletedSecret, SecretAttributes -class SecretClient(_KeyVaultClientBase): +class SecretClient(KeyVaultClientBase): """SecretClient is a high-level interface for managing a vault's secrets. Example: diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_models.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_models.py index 9436afc6c8fa..a8629aeeee28 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_models.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_models.py @@ -2,11 +2,18 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - import datetime -from typing import Any, Dict, Mapping, Optional -from ._generated.v7_0 import models -from ._internal import _parse_vault_id + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Any, Dict, Mapping, Optional + +from ._shared import parse_vault_id +from ._shared._generated.v7_0 import models class SecretAttributes(object): @@ -16,7 +23,7 @@ def __init__(self, attributes, vault_id, **kwargs): # type: (models.SecretAttributes, str, Mapping[str, Any]) -> None self._attributes = attributes self._id = vault_id - self._vault_id = _parse_vault_id(vault_id) + self._vault_id = parse_vault_id(vault_id) self._content_type = kwargs.get("content_type", None) self._key_id = kwargs.get("key_id", None) self._managed = kwargs.get("managed", None) diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py new file mode 100644 index 000000000000..beb24c202495 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/__init__.py @@ -0,0 +1,56 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from collections import namedtuple + +try: + import urllib.parse as parse +except ImportError: + # pylint:disable=import-error + import urlparse as parse # type: ignore + +from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase +from .client_base import KeyVaultClientBase +from .http_challenge import HttpChallenge +from . import http_challenge_cache as HttpChallengeCache + +__all__ = [ + "ChallengeAuthPolicy", + "ChallengeAuthPolicyBase", + "HttpChallenge", + "HttpChallengeCache", + "KeyVaultClientBase", +] + +_VaultId = namedtuple("VaultId", ["vault_url", "collection", "name", "version"]) + + +def parse_vault_id(url): + try: + parsed_uri = parse.urlparse(url) + except Exception: # pylint: disable=broad-except + raise ValueError("'{}' is not not a valid url".format(url)) + if not (parsed_uri.scheme and parsed_uri.hostname): + raise ValueError("'{}' is not not a valid url".format(url)) + + path = list(filter(None, parsed_uri.path.split("/"))) + + if len(path) < 2 or len(path) > 3: + raise ValueError("'{}' is not not a valid vault url".format(url)) + + return _VaultId( + vault_url="{}://{}".format(parsed_uri.scheme, parsed_uri.hostname), + collection=path[0], + name=path[1], + version=path[2] if len(path) == 3 else None, + ) + + +try: + from .async_challenge_auth_policy import AsyncChallengeAuthPolicy + from .async_client_base import AsyncKeyVaultClientBase, AsyncPagingAdapter + + __all__.extend(["AsyncChallengeAuthPolicy", "AsyncKeyVaultClientBase", "AsyncPagingAdapter"]) +except (SyntaxError, ImportError): + pass diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/key_vault_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/key_vault_client.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/_configuration.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/_configuration.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/_configuration.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/_configuration.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/_key_vault_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/_key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/_key_vault_client.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/_key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/_configuration_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/_configuration_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/_configuration_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/_configuration_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/_key_vault_client_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/_key_vault_client_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/_key_vault_client_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/_key_vault_client_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/operations_async/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/operations_async/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/operations_async/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/operations_async/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/aio/operations_async/_key_vault_client_operations_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_key_vault_client_enums.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_key_vault_client_enums.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_key_vault_client_enums.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_key_vault_client_enums.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_models.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_models.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_models.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_models_py3.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_models_py3.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_models_py3.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_models_py3.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_paged_models.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_paged_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/models/_paged_models.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/models/_paged_models.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/operations/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/operations/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/operations/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/operations/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/operations/_key_vault_client_operations.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/operations/_key_vault_client_operations.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/operations/_key_vault_client_operations.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/operations/_key_vault_client_operations.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/version.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/version.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v2016_10_01/version.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v2016_10_01/version.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/_configuration.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/_configuration.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/_configuration.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/_configuration.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/_key_vault_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/_key_vault_client.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/_key_vault_client.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/_key_vault_client.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/_configuration_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/_configuration_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/_configuration_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/_configuration_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/_key_vault_client_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/_key_vault_client_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/_key_vault_client_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/_key_vault_client_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/operations_async/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/operations_async/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/operations_async/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/operations_async/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/aio/operations_async/_key_vault_client_operations_async.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_key_vault_client_enums.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_key_vault_client_enums.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_key_vault_client_enums.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_key_vault_client_enums.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_models.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_models.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_models.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_models_py3.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_models_py3.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_models_py3.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_models_py3.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_paged_models.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_paged_models.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/models/_paged_models.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/models/_paged_models.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/operations/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/operations/__init__.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/operations/__init__.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/operations/__init__.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/operations/_key_vault_client_operations.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/operations/_key_vault_client_operations.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/operations/_key_vault_client_operations.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/operations/_key_vault_client_operations.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/version.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/version.py similarity index 100% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_generated/v7_0/version.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/_generated/v7_0/version.py diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py new file mode 100644 index 000000000000..d07718d9c5e6 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py @@ -0,0 +1,57 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.pipeline.transport import HttpRequest, HttpResponse + +from . import ChallengeAuthPolicyBase, HttpChallenge, HttpChallengeCache + + +class AsyncChallengeAuthPolicy(ChallengeAuthPolicyBase, AsyncHTTPPolicy): + """policy for handling HTTP authentication challenges""" + + async def send(self, request: PipelineRequest) -> HttpResponse: + challenge = HttpChallengeCache.get_challenge_for_url(request.http_request.url) + if not challenge: + # provoke a challenge with an unauthorized, bodiless request + no_body = HttpRequest( + request.http_request.method, request.http_request.url, headers=request.http_request.headers + ) + if request.http_request.body: + # no_body was created with request's headers -> if request has a body, no_body's content-length is wrong + no_body.headers["Content-Length"] = "0" + + challenger = await self.next.send(PipelineRequest(http_request=no_body, context=request.context)) + try: + challenge = self._update_challenge(request, challenger) + except ValueError: + # didn't receive the expected challenge -> nothing more this policy can do + return challenger + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + if response.http_response.status_code == 401: + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = self._update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + await self._handle_challenge(request, challenge) + response = await self.next.send(request) + + return response + + async def _handle_challenge(self, request: PipelineRequest, challenge: HttpChallenge) -> None: + """authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_resource() + if not scope.endswith("/.default"): + scope += "/.default" + + access_token = await self._credential.get_token(scope) + self._update_headers(request.http_request.headers, access_token.token) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_internal.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_client_base.py similarity index 92% rename from sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_internal.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_client_base.py index dadce35f902e..7424b5162ea3 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/_internal.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/async_client_base.py @@ -6,12 +6,11 @@ from azure.core.async_paging import AsyncPagedMixin from azure.core.configuration import Configuration from azure.core.pipeline import AsyncPipeline -from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy from azure.core.pipeline.transport import AsyncioRequestsTransport, HttpTransport from msrest.serialization import Model -from azure.keyvault.keys._generated import KeyVaultClient -from azure.keyvault.keys._internal import KEY_VAULT_SCOPE +from ._generated import KeyVaultClient +from . import AsyncChallengeAuthPolicy if TYPE_CHECKING: @@ -41,7 +40,7 @@ async def __anext__(self) -> Any: # TODO: expected type Model got Coroutine instead? -class _AsyncKeyVaultClientBase: +class AsyncKeyVaultClientBase: """ :param credential: A credential or credential provider which can be used to authenticate to the vault, a ValueError will be raised if the entity is not provided @@ -58,7 +57,7 @@ def create_config( if api_version is None: api_version = KeyVaultClient.DEFAULT_API_VERSION config = KeyVaultClient.get_configuration_class(api_version, aio=True)(credential, **kwargs) - config.authentication_policy = AsyncBearerTokenCredentialPolicy(credential, KEY_VAULT_SCOPE) + config.authentication_policy = AsyncChallengeAuthPolicy(credential) return config def __init__( diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py new file mode 100644 index 000000000000..204a59ddc05e --- /dev/null +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/challenge_auth_policy.py @@ -0,0 +1,91 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint:disable=unused-import + from azure.core.pipeline.transport import HttpResponse + +from azure.core.pipeline import PipelineRequest +from azure.core.pipeline.policies import HTTPPolicy +from azure.core.pipeline.policies.authentication import _BearerTokenCredentialPolicyBase +from azure.core.pipeline.transport import HttpRequest + +from .http_challenge import HttpChallenge +from . import http_challenge_cache as ChallengeCache + + +class ChallengeAuthPolicyBase(_BearerTokenCredentialPolicyBase): + """Sans I/O base for challenge authentication policies""" + + def __init__(self, credential, **kwargs): + super(ChallengeAuthPolicyBase, self).__init__(credential, **kwargs) + + @staticmethod + def _update_challenge(request, challenger): + # type: (HttpRequest, HttpResponse) -> HttpChallenge + """parse challenge from challenger, cache it, return it""" + + challenge = HttpChallenge( + request.http_request.url, + challenger.http_response.headers.get("WWW-Authenticate"), + response_headers=challenger.http_response.headers, + ) + ChallengeCache.set_challenge_for_url(request.http_request.url, challenge) + return challenge + + +class ChallengeAuthPolicy(ChallengeAuthPolicyBase, HTTPPolicy): + """policy for handling HTTP authentication challenges""" + + def send(self, request): + # type: (PipelineRequest) -> HttpResponse + + challenge = ChallengeCache.get_challenge_for_url(request.http_request.url) + if not challenge: + # provoke a challenge with an unauthorized, bodiless request + no_body = HttpRequest( + request.http_request.method, request.http_request.url, headers=request.http_request.headers + ) + if request.http_request.body: + # no_body was created with request's headers -> if request has a body, no_body's content-length is wrong + no_body.headers["Content-Length"] = "0" + + challenger = self.next.send(PipelineRequest(http_request=no_body, context=request.context)) + try: + challenge = self._update_challenge(request, challenger) + except ValueError: + # didn't receive the expected challenge -> nothing more this policy can do + return challenger + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + if response.http_response.status_code == 401: + # cached challenge could be outdated; maybe this response has a new one? + try: + challenge = self._update_challenge(request, response) + except ValueError: + # 401 with no legible challenge -> nothing more this policy can do + return response + + self._handle_challenge(request, challenge) + response = self.next.send(request) + + return response + + def _handle_challenge(self, request, challenge): + # type: (PipelineRequest, HttpChallenge) -> None + """authenticate according to challenge, add Authorization header to request""" + + scope = challenge.get_resource() + if not scope.endswith("/.default"): + scope += "/.default" + + access_token = self._credential.get_token(scope) + self._update_headers(request.http_request.headers, access_token.token) diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_internal.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/client_base.py similarity index 74% rename from sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_internal.py rename to sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/client_base.py index 67a6a9d71a16..147dc0506b76 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_internal.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/client_base.py @@ -2,11 +2,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from collections import namedtuple from typing import TYPE_CHECKING from azure.core import Configuration from azure.core.pipeline import Pipeline -from azure.core.pipeline.policies import BearerTokenCredentialPolicy from azure.core.pipeline.transport import RequestsTransport from ._generated import KeyVaultClient @@ -16,40 +14,13 @@ from azure.core.credentials import TokenCredential from azure.core.pipeline.transport import HttpTransport -try: - import urllib.parse as parse -except ImportError: - import urlparse as parse # pylint: disable=import-error - - -_VaultId = namedtuple("VaultId", ["vault_url", "collection", "name", "version"]) +from .challenge_auth_policy import ChallengeAuthPolicy KEY_VAULT_SCOPE = "https://vault.azure.net/.default" -def _parse_vault_id(url): - try: - parsed_uri = parse.urlparse(url) - except Exception: # pylint: disable=broad-except - raise ValueError("'{}' is not not a valid url".format(url)) - if not (parsed_uri.scheme and parsed_uri.hostname): - raise ValueError("'{}' is not not a valid url".format(url)) - - path = list(filter(None, parsed_uri.path.split("/"))) - - if len(path) < 2 or len(path) > 3: - raise ValueError("'{}' is not not a valid vault url".format(url)) - - return _VaultId( - vault_url="{}://{}".format(parsed_uri.scheme, parsed_uri.hostname), - collection=path[0], - name=path[1], - version=path[2] if len(path) == 3 else None, - ) - - -class _KeyVaultClientBase(object): +class KeyVaultClientBase(object): """ :param credential: A credential or credential provider which can be used to authenticate to the vault, a ValueError will be raised if the entity is not provided @@ -65,7 +36,7 @@ def create_config(credential, api_version=None, **kwargs): if api_version is None: api_version = KeyVaultClient.DEFAULT_API_VERSION config = KeyVaultClient.get_configuration_class(api_version, aio=False)(credential, **kwargs) - config.authentication_policy = BearerTokenCredentialPolicy(credential, KEY_VAULT_SCOPE) + config.authentication_policy = ChallengeAuthPolicy(credential) return config def __init__(self, vault_url, credential, config=None, transport=None, api_version=None, **kwargs): diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py new file mode 100644 index 000000000000..b2e67c71a3ae --- /dev/null +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge.py @@ -0,0 +1,113 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +try: + import urllib.parse as parse +except ImportError: + import urlparse as parse # type: ignore + + +class HttpChallenge(object): + def __init__(self, request_uri, challenge, response_headers=None): + """ Parses an HTTP WWW-Authentication Bearer challenge from a server. """ + self.source_authority = self._validate_request_uri(request_uri) + self.source_uri = request_uri + self._parameters = {} + + # get the scheme of the challenge and remove from the challenge string + trimmed_challenge = self._validate_challenge(challenge) + split_challenge = trimmed_challenge.split(" ", 1) + self.scheme = split_challenge[0] + trimmed_challenge = split_challenge[1] + + # split trimmed challenge into comma-separated name=value pairs. Values are expected + # to be surrounded by quotes which are stripped here. + for item in trimmed_challenge.split(","): + # process name=value pairs + comps = item.split("=") + if len(comps) == 2: + key = comps[0].strip(' "') + value = comps[1].strip(' "') + if key: + self._parameters[key] = value + + # minimum set of parameters + if not self._parameters: + raise ValueError("Invalid challenge parameters") + + # must specify authorization or authorization_uri + if "authorization" not in self._parameters and "authorization_uri" not in self._parameters: + raise ValueError("Invalid challenge parameters") + + # if the response headers were supplied + if response_headers: + # get the message signing key and message key encryption key from the headers + self.server_signature_key = response_headers.get("x-ms-message-signing-key", None) + self.server_encryption_key = response_headers.get("x-ms-message-encryption-key", None) + + def is_bearer_challenge(self): + """ Tests whether the HttpChallenge a Bearer challenge. + rtype: bool """ + if not self.scheme: + return False + + return self.scheme.lower() == "bearer" + + def is_pop_challenge(self): + """ Tests whether the HttpChallenge is a proof of possession challenge. + rtype: bool """ + if not self.scheme: + return False + + return self.scheme.lower() == "pop" + + def get_value(self, key): + return self._parameters.get(key) + + def get_authorization_server(self): + """ Returns the URI for the authorization server if present, otherwise empty string. """ + value = "" + for key in ["authorization_uri", "authorization"]: + value = self.get_value(key) or "" + if value: + break + return value + + def get_resource(self): + """ Returns the resource if present, otherwise empty string. """ + return self.get_value("resource") or "" + + def get_scope(self): + """ Returns the scope if present, otherwise empty string. """ + return self.get_value("scope") or "" + + def supports_pop(self): + """ Returns True if challenge supports pop token auth else False """ + return self._parameters.get("supportspop", "").lower() == "true" + + def supports_message_protection(self): + """ Returns True if challenge vault supports message protection """ + return self.supports_pop() and self.server_encryption_key and self.server_signature_key + + def _validate_challenge(self, challenge): + """ Verifies that the challenge is a valid auth challenge and returns the key=value pairs. """ + if not challenge: + raise ValueError("Challenge cannot be empty") + + return challenge.strip() + + # pylint: disable=no-self-use + def _validate_request_uri(self, uri): + """ Extracts the host authority from the given URI. """ + if not uri: + raise ValueError("request_uri cannot be empty") + + uri = parse.urlparse(uri) + if not uri.netloc: + raise ValueError("request_uri must be an absolute URI") + + if uri.scheme.lower() not in ["http", "https"]: + raise ValueError("request_uri must be HTTP or HTTPS") + + return uri.netloc diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge_cache.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge_cache.py new file mode 100644 index 000000000000..07cda1366aa8 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/_shared/http_challenge_cache.py @@ -0,0 +1,89 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import threading + +try: + import urllib.parse as parse +except ImportError: + import urlparse as parse # type: ignore + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint: disable=unused-import + from typing import Dict + from .http_challenge import HttpChallenge + + +_cache = {} # type: Dict[str, HttpChallenge] +_lock = threading.Lock() + + +def get_challenge_for_url(url): + """ Gets the challenge for the cached URL. + :param url: the URL the challenge is cached for. + :rtype: HttpBearerChallenge """ + + if not url: + raise ValueError("URL cannot be None") + + key = _get_cache_key(url) + + with _lock: + return _cache.get(key) + + +def _get_cache_key(url): + """Use the URL's netloc as cache key except when the URL specifies the default port for its scheme. In that case + use the netloc without the port. That is to say, https://foo.bar and https://foo.bar:443 are considered equivalent. + + This equivalency prevents an unnecessary challenge when using Key Vault's paging API. The Key Vault client doesn't + specify ports, but Key Vault's next page links do, so a redundant challenge would otherwise be executed when the + client requests the next page.""" + + parsed = parse.urlparse(url) + if parsed.scheme == "https" and parsed.port == 443: + return parsed.netloc[:-4] + return parsed.netloc + + +def remove_challenge_for_url(url): + """ Removes the cached challenge for the specified URL. + :param url: the URL for which to remove the cached challenge """ + if not url: + raise ValueError("URL cannot be empty") + + url = parse.urlparse(url) + + with _lock: + del _cache[url.netloc] + + +def set_challenge_for_url(url, challenge): + """ Caches the challenge for the specified URL. + :param url: the URL for which to cache the challenge + :param challenge: the challenge to cache """ + if not url: + raise ValueError("URL cannot be empty") + + if not challenge: + raise ValueError("Challenge cannot be empty") + + src_url = parse.urlparse(url) + if src_url.netloc != challenge.source_authority: + raise ValueError("Source URL and Challenge URL do not match") + + with _lock: + _cache[src_url.netloc] = challenge + + +def clear(): + """ Clears the cache. """ + + with _lock: + _cache.clear() diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/__init__.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/__init__.py index 652f08e501f6..44967833f2df 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/__init__.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/__init__.py @@ -3,6 +3,5 @@ # Licensed under the MIT License. # ------------------------------------ from ._client import SecretClient -from .._models import Secret, SecretAttributes, DeletedSecret __all__ = ["SecretClient"] diff --git a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py index 92502763e6cd..d7cf860c4309 100644 --- a/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-secrets/azure/keyvault/secrets/aio/_client.py @@ -8,10 +8,10 @@ from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError from azure.keyvault.secrets._models import Secret, DeletedSecret, SecretAttributes -from ._internal import _AsyncKeyVaultClientBase, AsyncPagingAdapter +from .._shared import AsyncKeyVaultClientBase, AsyncPagingAdapter -class SecretClient(_AsyncKeyVaultClientBase): +class SecretClient(AsyncKeyVaultClientBase): """SecretClient is a high-level interface for managing a vault's secrets. Example: diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations.py b/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations.py index 05afec5f7581..22fb91df9658 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations.py @@ -1,6 +1,6 @@ import time import os -from azure.keyvault import SecretClient +from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError @@ -42,7 +42,7 @@ def run_sample(): # Let's create a secret holding storage account credentials. # if the secret already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - secret = client.set_secret("secretName", "secretValue") + secret = client.set_secret("backupRestoreSecretName", "backupRestoreSecretValue") print("Secret with name '{0}' created with value '{1}'".format(secret.name, secret.value)) # Backups are good to have, if in case secrets gets deleted accidentally. diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations_async.py b/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations_async.py index 17516ce4484e..ff6861133e07 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations_async.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/backup_restore_operations_async.py @@ -42,7 +42,7 @@ async def run_sample(): # Let's create a secret holding storage account credentials. # if the secret already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - secret = await client.set_secret("secretName", "secretValue") + secret = await client.set_secret("backupRestoreSecretName", "backupRestoreSecretValue") print("Secret with name '{0}' created with value '{1}'".format(secret.name, secret.value)) # Backups are good to have, if in case secrets gets deleted accidentally. diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/hello_world.py b/sdk/keyvault/azure-keyvault-secrets/samples/hello_world.py index 3d673aa8332a..f75a3e4b2035 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/hello_world.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/hello_world.py @@ -1,6 +1,6 @@ import datetime import os -from azure.keyvault import SecretClient +from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError @@ -44,7 +44,7 @@ def run_sample(): # if the secret already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") expires = datetime.datetime.utcnow() + datetime.timedelta(days=365) - secret = client.set_secret("secretName", "secretValue", expires=expires) + secret = client.set_secret("helloWorldSecretName", "helloWorldSecretValue", expires=expires) print("Secret with name '{0}' created with value '{1}'".format(secret.name, secret.value)) print("Secret with name '{0}' expires on '{1}'".format(secret.name, secret.expires)) diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/hello_world_async.py b/sdk/keyvault/azure-keyvault-secrets/samples/hello_world_async.py index cfafc799866f..6dc51ccc6a38 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/hello_world_async.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/hello_world_async.py @@ -46,7 +46,7 @@ async def run_sample(): # if the secret already exists in the key vault, then a new version of the secret is created. print("\n1. Create Secret") expires = datetime.datetime.now(pytz.timezone("America/New_York")) + datetime.timedelta(days=365) - secret = await client.set_secret("secretName", "secretValue", expires=expires) + secret = await client.set_secret("helloWorldSecretName", "helloWorldSecretValue", expires=expires) print("Secret with name '{0}' created with value '{1}'".format(secret.name, secret.value)) print("Secret with name '{0}' expires on '{1}'".format(secret.name, secret.expires)) diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/list_operations.py b/sdk/keyvault/azure-keyvault-secrets/samples/list_operations.py index 4508d5faad07..609fe3026d04 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/list_operations.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/list_operations.py @@ -1,6 +1,6 @@ import time import os -from azure.keyvault import SecretClient +from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError @@ -42,8 +42,8 @@ def run_sample(): # Let's create secrets holding storage and bank accounts credentials. If the secret # already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - bank_secret = client.set_secret("bankSecretName", "secretValue1") - storage_secret = client.set_secret("storageSecretName", "secretValue2") + bank_secret = client.set_secret("listOpsBankSecretName", "listOpsSecretValue1") + storage_secret = client.set_secret("listOpsStorageSecretName", "listOpsSecretValue2") print("Secret with name '{0}' was created.".format(bank_secret.name)) print("Secret with name '{0}' was created.".format(storage_secret.name)) diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/list_operations_async.py b/sdk/keyvault/azure-keyvault-secrets/samples/list_operations_async.py index ecb3090603e8..5dd3f1302fb9 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/list_operations_async.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/list_operations_async.py @@ -43,8 +43,8 @@ async def run_sample(): # Let's create secrets holding storage and bank accounts credentials. If the secret # already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - bank_secret = await client.set_secret("bankSecretName", "secretValue1") - storage_secret = await client.set_secret("storageSecretName", "secretValue2") + bank_secret = await client.set_secret("listOpsBankSecretName", "listOpsSecretValue1") + storage_secret = await client.set_secret("listOpsStorageSecretName", "listOpsSecretValue2") print("Secret with name '{0}' was created.".format(bank_secret.name)) print("Secret with name '{0}' was created.".format(storage_secret.name)) diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations.py b/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations.py index f9be04e4e979..924d722e7b47 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations.py @@ -1,6 +1,6 @@ import time import os -from azure.keyvault import SecretClient +from azure.keyvault.secrets import SecretClient from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError @@ -42,8 +42,8 @@ def run_sample(): # Let's create secrets holding storage and bank accounts credentials. If the secret # already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - bank_secret = client.set_secret("bankSecretName", "secretValue1") - storage_secret = client.set_secret("storageSecretName", "secretValue2") + bank_secret = client.set_secret("recoverPurgeBankSecretName", "recoverPurgeSecretValue1") + storage_secret = client.set_secret("recoverPurgeStorageSecretName", "recoverPurgeSecretValue2") print("Secret with name '{0}' was created.".format(bank_secret.name)) print("Secret with name '{0}' was created.".format(storage_secret.name)) diff --git a/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations_async.py b/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations_async.py index cc86154a1da0..e59c3eba1552 100644 --- a/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations_async.py +++ b/sdk/keyvault/azure-keyvault-secrets/samples/recover_purge_operations_async.py @@ -42,8 +42,8 @@ async def run_sample(): # Let's create secrets holding storage and bank accounts credentials. If the secret # already exists in the Key Vault, then a new version of the secret is created. print("\n1. Create Secret") - bank_secret = await client.set_secret("bankSecretName", "secretValue1") - storage_secret = await client.set_secret("storageSecretName", "secretValue2") + bank_secret = await client.set_secret("recoverPurgeBankSecretName", "recoverPurgeSecretValue1") + storage_secret = await client.set_secret("recoverPurgeStorageSecretName", "recoverPurgeSecretValue2") print("Secret with name '{0}' was created.".format(bank_secret.name)) print("Secret with name '{0}' was created.".format(storage_secret.name)) diff --git a/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client.py b/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client.py index c0ed96a197ce..4ce440dea4f6 100644 --- a/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client.py +++ b/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client.py @@ -7,7 +7,7 @@ except ImportError: TYPE_CHECKING = False -from azure.keyvault.secrets._internal import _KeyVaultClientBase +from azure.keyvault.secrets._shared import KeyVaultClientBase from azure.keyvault.secrets import SecretClient if TYPE_CHECKING: @@ -18,7 +18,7 @@ from typing import Any, Optional -class VaultClient(_KeyVaultClientBase): +class VaultClient(KeyVaultClientBase): def __init__(self, vault_url, credential, config=None, transport=None, api_version=None, **kwargs): # type: (str, TokenCredential, Configuration, Optional[HttpTransport], Optional[str], **Any) -> None super(VaultClient, self).__init__( diff --git a/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client_async.py b/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client_async.py index 26208474b62d..f51f22570077 100644 --- a/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client_async.py +++ b/sdk/keyvault/azure-keyvault-secrets/tests/secrets_vault_client_async.py @@ -10,9 +10,8 @@ from azure.core.pipeline.transport import AsyncioRequestsTransport, HttpTransport from msrest.serialization import Model -from azure.keyvault.secrets._generated import KeyVaultClient +from azure.keyvault.secrets._shared import AsyncKeyVaultClientBase from azure.keyvault.secrets.aio import SecretClient -from azure.keyvault.secrets.aio._internal import _AsyncKeyVaultClientBase if TYPE_CHECKING: try: @@ -24,7 +23,7 @@ KEY_VAULT_SCOPE = "https://vault.azure.net/.default" -class VaultClient(_AsyncKeyVaultClientBase): +class VaultClient(AsyncKeyVaultClientBase): def __init__( self, vault_url: str, diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/__init__.py b/sdk/storage/azure-storage-blob/azure/storage/blob/__init__.py index ac1778e8a746..9e25b4982ba5 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/__init__.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/__init__.py @@ -14,13 +14,13 @@ from .lease import LeaseClient from .polling import CopyStatusPoller from ._shared.policies import ExponentialRetry, LinearRetry, NoRetry +from ._shared.download_chunking import StorageStreamDownloader from ._shared.models import( LocationMode, ResourceTypes, AccountPermissions, StorageErrorCode ) -from ._blob_utils import StorageStreamDownloader from .models import ( BlobType, BlockState, diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_utils.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_utils.py index b6dddaddb9cc..a2305be44e11 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_utils.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_utils.py @@ -6,16 +6,17 @@ # pylint: disable=no-self-use import sys -from io import BytesIO, SEEK_SET, UnsupportedOperation +from io import SEEK_SET, UnsupportedOperation from typing import Optional, Union, Any, TypeVar, TYPE_CHECKING # pylint: disable=unused-import import six from azure.core.exceptions import ResourceExistsError, ResourceModifiedError -from ._shared.utils import ( +from ._shared.request_handlers import validate_and_format_range_headers +from ._shared.response_handlers import ( process_storage_error, - validate_and_format_range_headers, parse_length_from_content_range, + deserialize_metadata, return_response_headers) from ._shared.models import StorageErrorCode, ModifiedAccessConditions from ._shared.upload_chunking import ( @@ -24,13 +25,7 @@ BlockBlobChunkUploader, PageBlobChunkUploader, AppendBlobChunkUploader) -from ._shared.download_chunking import ( - process_content, - process_range_and_offset, - ParallelBlobChunkDownloader, - SequentialBlobChunkDownloader -) -from ._shared.encryption import _generate_blob_encryption_data, _encrypt_blob +from ._shared.encryption import generate_blob_encryption_data, encrypt_blob from ._generated.models import ( StorageErrorException, BlockLookupList, @@ -99,7 +94,7 @@ def get_modification_conditions( if_match=if_match, if_none_match=if_none_match ) - return None + return ModifiedAccessConditions() def upload_block_blob( # pylint: disable=too-many-locals @@ -136,7 +131,7 @@ def upload_block_blob( # pylint: disable=too-many-locals except AttributeError: pass if key_encryption_key: - encryption_data, data = _encrypt_blob(data, key_encryption_key) + encryption_data, data = encrypt_blob(data, key_encryption_key) headers['x-ms-meta-encryptiondata'] = encryption_data return client.upload( data, @@ -161,7 +156,7 @@ def upload_block_blob( # pylint: disable=too-many-locals if use_original_upload_path: if key_encryption_key: - cek, iv, encryption_data = _generate_blob_encryption_data(key_encryption_key) + cek, iv, encryption_data = generate_blob_encryption_data(key_encryption_key) headers['x-ms-meta-encryptiondata'] = encryption_data block_ids = upload_blob_chunks( blob_service=client, @@ -385,11 +380,6 @@ def upload_append_blob( process_storage_error(error) -def deserialize_metadata(response, _, headers): # pylint: disable=unused-argument - raw_metadata = {k: v for k, v in response.headers.items() if k.startswith("x-ms-meta-")} - return {k[10:]: v for k, v in raw_metadata.items()} - - def deserialize_blob_properties(response, obj, headers): metadata = deserialize_metadata(response, obj, headers) blob_properties = BlobProperties( @@ -417,286 +407,3 @@ def deserialize_container_properties(response, obj, headers): **headers ) return container_properties - - -class StorageStreamDownloader(object): # pylint: disable=too-many-instance-attributes - """A streaming object to download a blob. - - The stream downloader can iterated, or download to open file or stream - over multiple threads. - """ - - def __init__( - self, name, container, service, config, offset, length, validate_content, - access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs - ): - self.service = service - self.config = config - self.offset = offset - self.length = length - self.timeout = timeout - self.validate_content = validate_content - self.access_conditions = access_conditions - self.mod_conditions = mod_conditions - self.require_encryption = require_encryption - self.key_encryption_key = key_encryption_key - self.key_resolver_function = key_resolver_function - self.request_options = kwargs - self.location_mode = None - self._download_complete = False - - # The service only provides transactional MD5s for chunks under 4MB. - # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first - # chunk so a transactional MD5 can be retrieved. - self.first_get_size = self.config.max_single_get_size if not self.validate_content \ - else self.config.max_chunk_get_size - initial_request_start = self.offset if self.offset is not None else 0 - if self.length is not None and self.length - self.offset < self.first_get_size: - initial_request_end = self.length - else: - initial_request_end = initial_request_start + self.first_get_size - 1 - - self.initial_range, self.initial_offset = process_range_and_offset( - initial_request_start, - initial_request_end, - self.length, - self.key_encryption_key, - self.key_resolver_function) - - self.download_size = None - self.blob_size = None - self.blob = self._initial_request() - self.properties = self.blob.properties - self.properties.name = name - self.properties.container = container - # Set the content length to the download size instead of the size of - # the last range - self.properties.size = self.download_size - - # Overwrite the content range to the user requested range - self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.blob_size) - - # Overwrite the content MD5 as it is the MD5 for the last range instead - # of the stored MD5 - # TODO: Set to the stored MD5 when the service returns this - self.properties.content_md5 = None - - def __len__(self): - return self.download_size - - def __iter__(self): - if self.download_size == 0: - content = b"" - else: - content = process_content( - self.blob, - self.initial_offset[0], - self.initial_offset[1], - self.require_encryption, - self.key_encryption_key, - self.key_resolver_function) - - if content is not None: - yield content - if self._download_complete: - return - - end_blob = self.blob_size - if self.length is not None: - # Use the length unless it is over the end of the blob - end_blob = min(self.blob_size, self.length + 1) - - downloader = SequentialBlobChunkDownloader( - blob_service=self.service, - download_size=self.download_size, - chunk_size=self.config.max_chunk_get_size, - progress=self.first_get_size, - start_range=self.initial_range[1] + 1, # start where the first download ended - end_range=end_blob, - stream=None, - validate_content=self.validate_content, - access_conditions=self.access_conditions, - mod_conditions=self.mod_conditions, - timeout=self.timeout, - require_encryption=self.require_encryption, - key_encryption_key=self.key_encryption_key, - key_resolver_function=self.key_resolver_function, - use_location=self.location_mode, - cls=deserialize_blob_stream, - **self.request_options) - - for chunk in downloader.get_chunk_offsets(): - yield downloader.yield_chunk(chunk) - - def _initial_request(self): - range_header, range_validation = validate_and_format_range_headers( - self.initial_range[0], - self.initial_range[1], - start_range_required=False, - end_range_required=False, - check_content_md5=self.validate_content) - - try: - location_mode, blob = self.service.download( - timeout=self.timeout, - range=range_header, - range_get_content_md5=range_validation, - lease_access_conditions=self.access_conditions, - modified_access_conditions=self.mod_conditions, - validate_content=self.validate_content, - cls=deserialize_blob_stream, - data_stream_total=None, - download_stream_current=0, - **self.request_options) - - # Check the location we read from to ensure we use the same one - # for subsequent requests. - self.location_mode = location_mode - - # Parse the total blob size and adjust the download size if ranges - # were specified - self.blob_size = parse_length_from_content_range(blob.properties.content_range) - if self.length is not None: - # Use the length unless it is over the end of the blob - self.download_size = min(self.blob_size, self.length - self.offset + 1) - elif self.offset is not None: - self.download_size = self.blob_size - self.offset - else: - self.download_size = self.blob_size - - except StorageErrorException as error: - if self.offset is None and error.response.status_code == 416: - # Get range will fail on an empty blob. If the user did not - # request a range, do a regular get request in order to get - # any properties. - try: - _, blob = self.service.download( - timeout=self.timeout, - lease_access_conditions=self.access_conditions, - modified_access_conditions=self.mod_conditions, - validate_content=self.validate_content, - cls=deserialize_blob_stream, - data_stream_total=0, - download_stream_current=0, - **self.request_options) - except StorageErrorException as error: - process_storage_error(error) - - # Set the download size to empty - self.download_size = 0 - self.blob_size = 0 - else: - process_storage_error(error) - - # If the blob is small, the download is complete at this point. - # If blob size is large, download the rest of the blob in chunks. - if blob.properties.size != self.download_size: - # Lock on the etag. This can be overriden by the user by specifying '*' - if not self.mod_conditions: - self.mod_conditions = ModifiedAccessConditions() - if not self.mod_conditions.if_match: - self.mod_conditions.if_match = blob.properties.etag - else: - self._download_complete = True - - return blob - - - def content_as_bytes(self, max_connections=1): - """Download the contents of this blob. - - This operation is blocking until all data is downloaded. - - :param int max_connections: - The number of parallel connections with which to download. - :rtype: bytes - """ - stream = BytesIO() - self.download_to_stream(stream, max_connections=max_connections) - return stream.getvalue() - - def content_as_text(self, max_connections=1, encoding='UTF-8'): - """Download the contents of this blob, and decode as text. - - This operation is blocking until all data is downloaded. - - :param int max_connections: - The number of parallel connections with which to download. - :rtype: str - """ - content = self.content_as_bytes(max_connections=max_connections) - return content.decode(encoding) - - def download_to_stream(self, stream, max_connections=1): - """Download the contents of this blob to a stream. - - :param stream: - The stream to download to. This can be an open file-handle, - or any writable stream. The stream must be seekable if the download - uses more than one parallel connection. - :returns: The properties of the downloaded blob. - :rtype: ~azure.storage.blob.models.BlobProperties - """ - # the stream must be seekable if parallel download is required - if max_connections > 1: - error_message = "Target stream handle must be seekable." - if sys.version_info >= (3,) and not stream.seekable(): - raise ValueError(error_message) - - try: - stream.seek(stream.tell()) - except (NotImplementedError, AttributeError): - raise ValueError(error_message) - - if self.download_size == 0: - content = b"" - else: - content = process_content( - self.blob, - self.initial_offset[0], - self.initial_offset[1], - self.require_encryption, - self.key_encryption_key, - self.key_resolver_function) - # Write the content to the user stream - # Clear blob content since output has been written to user stream - if content is not None: - stream.write(content) - if self._download_complete: - return self.properties - - end_blob = self.blob_size - if self.length is not None: - # Use the length unless it is over the end of the blob - end_blob = min(self.blob_size, self.length + 1) - - downloader_class = ParallelBlobChunkDownloader if max_connections > 1 else SequentialBlobChunkDownloader - downloader = downloader_class( - blob_service=self.service, - download_size=self.download_size, - chunk_size=self.config.max_chunk_get_size, - progress=self.first_get_size, - start_range=self.initial_range[1] + 1, # start where the first download ended - end_range=end_blob, - stream=stream, - validate_content=self.validate_content, - access_conditions=self.access_conditions, - mod_conditions=self.mod_conditions, - timeout=self.timeout, - require_encryption=self.require_encryption, - key_encryption_key=self.key_encryption_key, - key_resolver_function=self.key_resolver_function, - use_location=self.location_mode, - cls=deserialize_blob_stream, - **self.request_options) - - if max_connections > 1: - import concurrent.futures - executor = concurrent.futures.ThreadPoolExecutor(max_connections) - list(executor.map(downloader.process_chunk, downloader.get_chunk_offsets())) - else: - for chunk in downloader.get_chunk_offsets(): - downloader.process_chunk(chunk) - - return self.properties diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py index 5b396cd202e8..160f88223820 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py @@ -3,3 +3,54 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- + +import base64 +import hashlib +import hmac + +try: + from urllib.parse import quote, unquote +except ImportError: + from urllib2 import quote, unquote # type: ignore + +import six + + +def url_quote(url): + return quote(url) + + +def url_unquote(url): + return unquote(url) + + +def encode_base64(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + encoded = base64.b64encode(data) + return encoded.decode('utf-8') + + +def decode_base64_to_bytes(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + return base64.b64decode(data) + + +def decode_base64_to_text(data): + decoded_bytes = decode_base64_to_bytes(data) + return decoded_bytes.decode('utf-8') + + +def sign_string(key, string_to_sign, key_is_base64=True): + if key_is_base64: + key = decode_base64_to_bytes(key) + else: + if isinstance(key, six.text_type): + key = key.encode('utf-8') + if isinstance(string_to_sign, six.text_type): + string_to_sign = string_to_sign.encode('utf-8') + signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) + digest = signed_hmac_sha256.digest() + encoded_digest = encode_base64(digest) + return encoded_digest diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py index 4a2c4532d924..e9de0de09a94 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py @@ -4,9 +4,6 @@ # license information. # -------------------------------------------------------------------------- -import base64 -import hashlib -import hmac import logging import sys try: @@ -18,43 +15,12 @@ from azure.core.exceptions import ClientAuthenticationError from azure.core.pipeline.policies import SansIOHTTPPolicy -if sys.version_info < (3,): - _unicode_type = unicode # pylint: disable=undefined-variable -else: - _unicode_type = str -logger = logging.getLogger(__name__) - - -def _encode_base64(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') +from . import sign_string -def _decode_base64_to_bytes(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - return base64.b64decode(data) - - -def _decode_base64_to_text(data): - decoded_bytes = _decode_base64_to_bytes(data) - return decoded_bytes.decode('utf-8') +logger = logging.getLogger(__name__) -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, _unicode_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, _unicode_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = _encode_base64(digest) - return encoded_digest # wraps a given exception with the desired exception type def _wrap_exception(ex, desired_type): @@ -125,7 +91,7 @@ def _get_canonicalized_resource_query(self, request): def _add_authorization_header(self, request, string_to_sign): try: - signature = _sign_string(self.account_key, string_to_sign) + signature = sign_string(self.account_key, string_to_sign) auth_string = 'SharedKey ' + self.account_name + ':' + signature request.http_request.headers['Authorization'] = auth_string except Exception as ex: diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py new file mode 100644 index 000000000000..2f6148afeb11 --- /dev/null +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/base_client.py @@ -0,0 +1,300 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging +try: + from urllib.parse import parse_qs +except ImportError: + from urlparse import parse_qs # type: ignore + +import six + +from azure.core import Configuration +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import RequestsTransport +from azure.core.pipeline.policies import ( + RedirectPolicy, + ContentDecodePolicy, + BearerTokenCredentialPolicy, + ProxyPolicy) + +from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT +from .models import LocationMode +from .authentication import SharedKeyCredentialPolicy +from .shared_access_signature import QueryStringConstants +from .policies import ( + StorageHeadersPolicy, + StorageUserAgentPolicy, + StorageContentValidation, + StorageRequestHook, + StorageResponseHook, + StorageLoggingPolicy, + StorageHosts, + QueueMessagePolicy, + ExponentialRetry) + + +_LOGGER = logging.getLogger(__name__) +_SERVICE_PARAMS = { + 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, + 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, + 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, +} + + +class StorageAccountHostsMixin(object): + + def __init__( + self, parsed_url, # type: Any + service, # type: str + credential=None, # type: Optional[Any] + **kwargs # type: Any + ): + # type: (...) -> None + self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) + self._hosts = kwargs.get('_hosts') + self.scheme = parsed_url.scheme + + if service not in ['blob', 'queue', 'file']: + raise ValueError("Invalid service: {}".format(service)) + account = parsed_url.netloc.split(".{}.core.".format(service)) + secondary_hostname = None + self.credential = format_shared_key_credential(account, credential) + if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): + raise ValueError("Token credential is only supported with HTTPS.") + if hasattr(self.credential, 'account_name'): + secondary_hostname = "{}-secondary.{}.{}".format( + self.credential.account_name, service, SERVICE_HOST_BASE) + + if not self._hosts: + if len(account) > 1: + secondary_hostname = parsed_url.netloc.replace( + account[0], + account[0] + '-secondary') + if kwargs.get('secondary_hostname'): + secondary_hostname = kwargs['secondary_hostname'] + self._hosts = { + LocationMode.PRIMARY: parsed_url.netloc, + LocationMode.SECONDARY: secondary_hostname} + + self.require_encryption = kwargs.get('require_encryption', False) + self.key_encryption_key = kwargs.get('key_encryption_key') + self.key_resolver_function = kwargs.get('key_resolver_function') + + self._config, self._pipeline = create_pipeline( + self.credential, storage_sdk=service, hosts=self._hosts, **kwargs) + + def __enter__(self): + self._client.__enter__() + return self + + def __exit__(self, *args): + self._client.__exit__(*args) + + @property + def url(self): + return self._format_url(self._hosts[self._location_mode]) + + @property + def primary_endpoint(self): + return self._format_url(self._hosts[LocationMode.PRIMARY]) + + @property + def primary_hostname(self): + return self._hosts[LocationMode.PRIMARY] + + @property + def secondary_endpoint(self): + if not self._hosts[LocationMode.SECONDARY]: + raise ValueError("No secondary host configured.") + return self._format_url(self._hosts[LocationMode.SECONDARY]) + + @property + def secondary_hostname(self): + return self._hosts[LocationMode.SECONDARY] + + @property + def location_mode(self): + return self._location_mode + + @location_mode.setter + def location_mode(self, value): + if self._hosts.get(value): + self._location_mode = value + self._client._config.url = self.url # pylint: disable=protected-access + else: + raise ValueError("No host URL for location mode: {}".format(value)) + + def _format_query_string(self, sas_token, credential, snapshot=None, share_snapshot=None): + query_str = "?" + if snapshot: + query_str += 'snapshot={}&'.format(self.snapshot) + if share_snapshot: + query_str += 'sharesnapshot={}&'.format(self.snapshot) + if sas_token and not credential: + query_str += sas_token + elif is_credential_sastoken(credential): + query_str += credential.lstrip('?') + credential = None + return query_str.rstrip('?&'), credential + + +def format_shared_key_credential(account, credential): + if isinstance(credential, six.string_types): + if len(account) < 2: + raise ValueError("Unable to determine account name for shared key credential.") + credential = { + 'account_name': account[0], + 'account_key': credential + } + if isinstance(credential, dict): + if 'account_name' not in credential: + raise ValueError("Shared key credential missing 'account_name") + if 'account_key' not in credential: + raise ValueError("Shared key credential missing 'account_key") + return SharedKeyCredentialPolicy(**credential) + return credential + + +def parse_connection_str(conn_str, credential, service): + conn_str = conn_str.rstrip(';') + conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension + endpoints = _SERVICE_PARAMS[service] + primary = None + secondary = None + if not credential: + try: + credential = { + 'account_name': conn_settings['AccountName'], + 'account_key': conn_settings['AccountKey'] + } + except KeyError: + credential = conn_settings.get('SharedAccessSignature') + if endpoints['primary'] in conn_settings: + primary = conn_settings[endpoints['primary']] + if endpoints['secondary'] in conn_settings: + secondary = conn_settings[endpoints['secondary']] + else: + if endpoints['secondary'] in conn_settings: + raise ValueError("Connection string specifies only secondary endpoint.") + try: + primary = "{}://{}.{}.{}".format( + conn_settings['DefaultEndpointsProtocol'], + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + secondary = "{}-secondary.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + except KeyError: + pass + + if not primary: + try: + primary = "https://{}.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) + ) + except KeyError: + raise ValueError("Connection string missing required connection details.") + return primary, secondary, credential + + +def create_configuration(**kwargs): + # type: (**Any) -> Configuration + if 'connection_timeout' not in kwargs: + kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT + config = Configuration(**kwargs) + config.headers_policy = StorageHeadersPolicy(**kwargs) + config.user_agent_policy = StorageUserAgentPolicy(**kwargs) + config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + config.redirect_policy = RedirectPolicy(**kwargs) + config.logging_policy = StorageLoggingPolicy(**kwargs) + config.proxy_policy = ProxyPolicy(**kwargs) + + # Storage settings + config.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) + config.copy_polling_interval = 15 + + # Block blob uploads + config.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) + config.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) + config.use_byte_buffer = kwargs.get('use_byte_buffer', False) + + # Page blob uploads + config.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) + + # Blob downloads + config.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) + config.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) + + # File uploads + config.max_range_size = kwargs.get('max_range_size', 4 * 1024 * 1024) + return config + + +def create_pipeline(credential, **kwargs): + # type: (Any, **Any) -> Tuple[Configuration, Pipeline] + credential_policy = None + if hasattr(credential, 'get_token'): + credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) + elif isinstance(credential, SharedKeyCredentialPolicy): + credential_policy = credential + elif credential is not None: + raise TypeError("Unsupported credential: {}".format(credential)) + + config = kwargs.get('_configuration') or create_configuration(**kwargs) + if kwargs.get('_pipeline'): + return config, kwargs['_pipeline'] + transport = kwargs.get('transport') # type: HttpTransport + if not transport: + transport = RequestsTransport(config) + policies = [ + QueueMessagePolicy(), + config.headers_policy, + config.user_agent_policy, + StorageContentValidation(), + StorageRequestHook(**kwargs), + credential_policy, + ContentDecodePolicy(), + config.redirect_policy, + StorageHosts(**kwargs), + config.retry_policy, + config.logging_policy, + StorageResponseHook(**kwargs), + ] + return config, Pipeline(transport, policies=policies) + + +def parse_query(query_str): + sas_values = QueryStringConstants.to_list() + parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} + sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] + sas_token = None + if sas_params: + sas_token = '&'.join(sas_params) + + snapshot = parsed_query.get('snapshot') or parsed_query.get('sharesnapshot') + return snapshot, sas_token + + +def is_credential_sastoken(credential): + if not credential or not isinstance(credential, six.string_types): + return False + + sas_values = QueryStringConstants.to_list() + parsed_query = parse_qs(credential.lstrip('?')) + if parsed_query and all([k in sas_values for k in parsed_query.keys()]): + return True + return False diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/download_chunking.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/download_chunking.py index 41d6fc0dafea..e923a992e314 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/download_chunking.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/download_chunking.py @@ -3,18 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- + +import sys import threading +from io import BytesIO from azure.core.exceptions import HttpResponseError from .models import ModifiedAccessConditions -from .utils import validate_and_format_range_headers, process_storage_error -from .encryption import _decrypt_blob +from .request_handlers import validate_and_format_range_headers +from .response_handlers import process_storage_error, parse_length_from_content_range +from .encryption import decrypt_blob -def process_range_and_offset(start_range, end_range, length, key_encryption_key, key_resolver_function): +def process_range_and_offset(start_range, end_range, length, encryption): start_offset, end_offset = 0, 0 - if key_encryption_key is not None or key_resolver_function is not None: + if encryption.get('key') is not None or encryption.get('resolver') is not None: if start_range is not None: # Align the start of the range along a 16 byte block start_offset = start_range % 16 @@ -35,68 +39,70 @@ def process_range_and_offset(start_range, end_range, length, key_encryption_key, return (start_range, end_range), (start_offset, end_offset) -def process_content(blob, start_offset, end_offset, require_encryption, key_encryption_key, key_resolver_function): - if key_encryption_key is not None or key_resolver_function is not None: +def process_content(data, start_offset, end_offset, encryption): + if encryption.get('key') is not None or encryption.get('resolver') is not None: try: - return _decrypt_blob( - require_encryption, - key_encryption_key, - key_resolver_function, - blob, + return decrypt_blob( + encryption.get('required'), + encryption.get('key'), + encryption.get('resolver'), + data, start_offset, end_offset) except Exception as error: raise HttpResponseError( message="Decryption failed.", - response=blob.response, + response=data.response, error=error) else: - return b"".join(list(blob)) + return b"".join(list(data)) -class _BlobChunkDownloader(object): # pylint: disable=too-many-instance-attributes +class _ChunkDownloader(object): def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, stream, - validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): - # identifiers for the blob - self.blob_service = blob_service + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + + self.service = service # information on the download range/chunk size self.chunk_size = chunk_size - self.download_size = download_size + self.total_size = total_size self.start_index = start_range - self.blob_end = end_range + self.end_index = end_range # the destination that we will write to self.stream = stream # download progress so far - self.progress_total = progress + self.progress_total = current_progress # encryption - self.require_encryption = require_encryption - self.key_encryption_key = key_encryption_key - self.key_resolver_function = key_resolver_function + self.encryption_options = encryption_options - # parameters for each get blob operation - self.timeout = timeout + # parameters for each get operation self.validate_content = validate_content - self.access_conditions = access_conditions - self.mod_conditions = mod_conditions self.request_options = kwargs def _calculate_range(self, chunk_start): - if chunk_start + self.chunk_size > self.blob_end: - chunk_end = self.blob_end + if chunk_start + self.chunk_size > self.end_index: + chunk_end = self.end_index else: chunk_end = chunk_start + self.chunk_size return chunk_start, chunk_end def get_chunk_offsets(self): index = self.start_index - while index < self.blob_end: + while index < self.end_index: yield index index += self.chunk_size @@ -122,57 +128,57 @@ def _write_to_stream(self, chunk_data, chunk_start): def _download_chunk(self, chunk_start, chunk_end): download_range, offset = process_range_and_offset( - chunk_start, - chunk_end, - chunk_end, - self.key_encryption_key, - self.key_resolver_function, - ) + chunk_start, chunk_end, chunk_end, self.encryption_options) range_header, range_validation = validate_and_format_range_headers( download_range[0], download_range[1] - 1, check_content_md5=self.validate_content) try: - _, response = self.blob_service.download( - timeout=self.timeout, + _, response = self.service.download( range=range_header, range_get_content_md5=range_validation, - lease_access_conditions=self.access_conditions, - modified_access_conditions=self.mod_conditions, validate_content=self.validate_content, - data_stream_total=self.download_size, + data_stream_total=self.total_size, download_stream_current=self.progress_total, **self.request_options) except HttpResponseError as error: process_storage_error(error) - chunk_data = process_content( - response, - offset[0], - offset[1], - self.require_encryption, - self.key_encryption_key, - self.key_resolver_function) + chunk_data = process_content(response, offset[0], offset[1], self.encryption_options) # This makes sure that if_match is set so that we can validate # that subsequent downloads are to an unmodified blob - if not self.mod_conditions: - self.mod_conditions = ModifiedAccessConditions() - self.mod_conditions.if_match = response.properties.etag + if self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = response.properties.etag + return chunk_data -class ParallelBlobChunkDownloader(_BlobChunkDownloader): - def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): +class ParallelChunkDownloader(_ChunkDownloader): - super(ParallelBlobChunkDownloader, self).__init__( - blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs) + def __init__( + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + super(ParallelChunkDownloader, self).__init__( + service=service, + total_size=total_size, + chunk_size=chunk_size, + current_progress=current_progress, + start_range=start_range, + end_range=end_range, + stream=stream, + validate_content=validate_content, + encryption_options=encryption_options, + **kwargs) # for a parallel download, the stream is always seekable, so we note down the current position # in order to seek to the right place when out-of-order chunks come in @@ -193,7 +199,7 @@ def _write_to_stream(self, chunk_data, chunk_start): self.stream.write(chunk_data) -class SequentialBlobChunkDownloader(_BlobChunkDownloader): +class SequentialChunkDownloader(_ChunkDownloader): def _update_progress(self, length): self.progress_total += length @@ -201,3 +207,254 @@ def _update_progress(self, length): def _write_to_stream(self, chunk_data, chunk_start): # chunk_start is ignored in the case of sequential download since we cannot seek the destination stream self.stream.write(chunk_data) + + +class StorageStreamDownloader(object): + """A streaming object to download from Azure Storage. + + The stream downloader can iterated, or download to open file or stream + over multiple threads. + """ + + def __init__( + self, service=None, + config=None, + offset=None, + length=None, + validate_content=None, + encryption_options=None, + extra_properties=None, + **kwargs): + self.service = service + self.config = config + self.offset = offset + self.length = length + self.validate_content = validate_content + self.encryption_options = encryption_options or {} + self.request_options = kwargs + self.location_mode = None + self._download_complete = False + + # The service only provides transactional MD5s for chunks under 4MB. + # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first + # chunk so a transactional MD5 can be retrieved. + self.first_get_size = self.config.max_single_get_size if not self.validate_content \ + else self.config.max_chunk_get_size + initial_request_start = self.offset if self.offset is not None else 0 + if self.length is not None and self.length - self.offset < self.first_get_size: + initial_request_end = self.length + else: + initial_request_end = initial_request_start + self.first_get_size - 1 + + self.initial_range, self.initial_offset = process_range_and_offset( + initial_request_start, initial_request_end, self.length, self.encryption_options) + + self.download_size = None + self.file_size = None + self.response = self._initial_request() + self.properties = self.response.properties + + # Set the content length to the download size instead of the size of + # the last range + self.properties.size = self.download_size + + # Overwrite the content range to the user requested range + self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.file_size) + + # Set additional properties according to download type + if extra_properties: + for prop, value in extra_properties.items(): + setattr(self.properties, prop, value) + + # Overwrite the content MD5 as it is the MD5 for the last range instead + # of the stored MD5 + # TODO: Set to the stored MD5 when the service returns this + self.properties.content_md5 = None + + def __len__(self): + return self.download_size + + def __iter__(self): + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + if content is not None: + yield content + if self._download_complete: + return + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader = SequentialBlobChunkDownloader( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + for chunk in downloader.get_chunk_offsets(): + yield downloader.yield_chunk(chunk) + + def _initial_request(self): + range_header, range_validation = validate_and_format_range_headers( + self.initial_range[0], + self.initial_range[1], + start_range_required=False, + end_range_required=False, + check_content_md5=self.validate_content) + + try: + location_mode, response = self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=None, + download_stream_current=0, + **self.request_options) + + # Check the location we read from to ensure we use the same one + # for subsequent requests. + self.location_mode = location_mode + + # Parse the total file size and adjust the download size if ranges + # were specified + self.file_size = parse_length_from_content_range(response.properties.content_range) + if self.length is not None: + # Use the length unless it is over the end of the file + self.download_size = min(self.file_size, self.length - self.offset + 1) + elif self.offset is not None: + self.download_size = self.file_size - self.offset + else: + self.download_size = self.file_size + + except HttpResponseError as error: + if self.offset is None and error.response.status_code == 416: + # Get range will fail on an empty file. If the user did not + # request a range, do a regular get request in order to get + # any properties. + try: + _, response = self.service.download( + validate_content=self.validate_content, + data_stream_total=0, + download_stream_current=0, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + # Set the download size to empty + self.download_size = 0 + self.file_size = 0 + else: + process_storage_error(error) + + # If the file is small, the download is complete at this point. + # If file size is large, download the rest of the file in chunks. + if response.properties.size != self.download_size: + # Lock on the etag. This can be overriden by the user by specifying '*' + if self.request_options.get('modified_access_conditions'): + if not self.request_options['modified_access_conditions'].if_match: + self.request_options['modified_access_conditions'].if_match = response.properties.etag + else: + self._download_complete = True + + return response + + + def content_as_bytes(self, max_connections=1): + """Download the contents of this file. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: bytes + """ + stream = BytesIO() + self.download_to_stream(stream, max_connections=max_connections) + return stream.getvalue() + + def content_as_text(self, max_connections=1, encoding='UTF-8'): + """Download the contents of this file, and decode as text. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: str + """ + content = self.content_as_bytes(max_connections=max_connections) + return content.decode(encoding) + + def download_to_stream(self, stream, max_connections=1): + """Download the contents of this file to a stream. + + :param stream: + The stream to download to. This can be an open file-handle, + or any writable stream. The stream must be seekable if the download + uses more than one parallel connection. + :returns: The properties of the downloaded file. + :rtype: Any + """ + # the stream must be seekable if parallel download is required + if max_connections > 1: + error_message = "Target stream handle must be seekable." + if sys.version_info >= (3,) and not stream.seekable(): + raise ValueError(error_message) + + try: + stream.seek(stream.tell()) + except (NotImplementedError, AttributeError): + raise ValueError(error_message) + + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + # Write the content to the user stream + if content is not None: + stream.write(content) + if self._download_complete: + return self.properties + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader_class = ParallelChunkDownloader if max_connections > 1 else SequentialChunkDownloader + downloader = downloader_class( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + if max_connections > 1: + import concurrent.futures + executor = concurrent.futures.ThreadPoolExecutor(max_connections) + list(executor.map(downloader.process_chunk, downloader.get_chunk_offsets())) + else: + for chunk in downloader.get_chunk_offsets(): + downloader.process_chunk(chunk) + + return self.properties diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/encryption.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/encryption.py index 222e213da627..b1178eaa9262 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/encryption.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/encryption.py @@ -21,22 +21,17 @@ from azure.core.exceptions import HttpResponseError from ..version import VERSION -from .authentication import _encode_base64, _decode_base64_to_bytes +from . import encode_base64, decode_base64_to_bytes _ENCRYPTION_PROTOCOL_V1 = '1.0' -_ERROR_VALUE_NONE = '{0} should not be None.' _ERROR_OBJECT_INVALID = \ '{0} does not define a complete interface. Value of {1} is either missing or invalid.' -_ERROR_DATA_NOT_ENCRYPTED = 'Encryption required, but received data does not contain appropriate metatadata.' + \ - 'Data was either not encrypted or metadata has been lost.' -_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM = \ - 'Specified encryption algorithm is not supported.' def _validate_not_none(param_name, param): if param is None: - raise ValueError(_ERROR_VALUE_NONE.format(param_name)) + raise ValueError('{0} should not be None.'.format(param_name)) def _validate_key_encryption_key_wrap(kek): @@ -147,7 +142,7 @@ def _generate_encryption_data_dict(kek, cek, iv): # Use OrderedDict to comply with Java's ordering requirement. wrapped_content_key = OrderedDict() wrapped_content_key['KeyId'] = kek.get_kid() - wrapped_content_key['EncryptedKey'] = _encode_base64(wrapped_cek) + wrapped_content_key['EncryptedKey'] = encode_base64(wrapped_cek) wrapped_content_key['Algorithm'] = kek.get_key_wrap_algorithm() encryption_agent = OrderedDict() @@ -157,7 +152,7 @@ def _generate_encryption_data_dict(kek, cek, iv): encryption_data_dict = OrderedDict() encryption_data_dict['WrappedContentKey'] = wrapped_content_key encryption_data_dict['EncryptionAgent'] = encryption_agent - encryption_data_dict['ContentEncryptionIV'] = _encode_base64(iv) + encryption_data_dict['ContentEncryptionIV'] = encode_base64(iv) encryption_data_dict['KeyWrappingMetadata'] = {'EncryptionLibrary': 'Python ' + VERSION} return encryption_data_dict @@ -180,7 +175,7 @@ def _dict_to_encryption_data(encryption_data_dict): raise ValueError("Unsupported encryption version.") wrapped_content_key = encryption_data_dict['WrappedContentKey'] wrapped_content_key = _WrappedContentKey(wrapped_content_key['Algorithm'], - _decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), + decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), wrapped_content_key['KeyId']) encryption_agent = encryption_data_dict['EncryptionAgent'] @@ -192,7 +187,7 @@ def _dict_to_encryption_data(encryption_data_dict): else: key_wrapping_metadata = None - encryption_data = _EncryptionData(_decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), + encryption_data = _EncryptionData(decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), encryption_agent, wrapped_content_key, key_wrapping_metadata) @@ -259,7 +254,49 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol return content_encryption_key -def _encrypt_blob(blob, key_encryption_key): +def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver=None): + ''' + Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. + Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). + Returns the original plaintex. + + :param str message: + The ciphertext to be decrypted. + :param _EncryptionData encryption_data: + The metadata associated with this ciphertext. + :param object key_encryption_key: + The user-provided key-encryption-key. Must implement the following methods: + unwrap_key(key, algorithm) + - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. + get_kid() + - returns a string key id for this key-encryption-key. + :param function resolver(kid): + The user-provided key resolver. Uses the kid string to return a key-encryption-key + implementing the interface defined above. + :return: The decrypted plaintext. + :rtype: str + ''' + _validate_not_none('message', message) + content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) + + if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: + raise ValueError('Specified encryption algorithm is not supported.') + + cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) + + # decrypt data + decrypted_data = message + decryptor = cipher.decryptor() + decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) + + # unpad data + unpadder = PKCS7(128).unpadder() + decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) + + return decrypted_data + + +def encrypt_blob(blob, key_encryption_key): ''' Encrypts the given blob using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -302,7 +339,7 @@ def _encrypt_blob(blob, key_encryption_key): return dumps(encryption_data), encrypted_data -def _generate_blob_encryption_data(key_encryption_key): +def generate_blob_encryption_data(key_encryption_key): ''' Generates the encryption_metadata for the blob. @@ -328,7 +365,7 @@ def _generate_blob_encryption_data(key_encryption_key): return content_encryption_key, initialization_vector, encryption_data -def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, +def decrypt_blob(require_encryption, key_encryption_key, key_resolver, response, start_offset, end_offset): ''' Decrypts the given blob contents and returns only the requested range. @@ -356,12 +393,14 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, encryption_data = _dict_to_encryption_data(loads(response.response.headers['x-ms-meta-encryptiondata'])) except: # pylint: disable=bare-except if require_encryption: - raise ValueError(_ERROR_DATA_NOT_ENCRYPTED) + raise ValueError( + 'Encryption required, but received data does not contain appropriate metatadata.' + \ + 'Data was either not encrypted or metadata has been lost.') return content if encryption_data.encryption_agent.encryption_algorithm != _EncryptionAlgorithm.AES_CBC_256: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) + raise ValueError('Specified encryption algorithm is not supported.') blob_type = response.response.headers['x-ms-blob-type'] @@ -407,7 +446,7 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, return content[start_offset: len(content) - end_offset] -def _get_blob_encryptor_and_padder(cek, iv, should_pad): +def get_blob_encryptor_and_padder(cek, iv, should_pad): encryptor = None padder = None @@ -419,7 +458,7 @@ def _get_blob_encryptor_and_padder(cek, iv, should_pad): return encryptor, padder -def _encrypt_queue_message(message, key_encryption_key): +def encrypt_queue_message(message, key_encryption_key): ''' Encrypts the given plain text message using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -459,7 +498,7 @@ def _encrypt_queue_message(message, key_encryption_key): encrypted_data = encryptor.update(padded_data) + encryptor.finalize() # Build the dictionary structure. - queue_message = {'EncryptedMessageContents': _encode_base64(encrypted_data), + queue_message = {'EncryptedMessageContents': encode_base64(encrypted_data), 'EncryptionData': _generate_encryption_data_dict(key_encryption_key, content_encryption_key, initialization_vector)} @@ -467,7 +506,7 @@ def _encrypt_queue_message(message, key_encryption_key): return dumps(queue_message) -def _decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): +def decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): ''' Returns the decrypted message contents from an EncryptedQueueMessage. If no encryption metadata is present, will return the unaltered message. @@ -492,7 +531,7 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption message = loads(message) encryption_data = _dict_to_encryption_data(message['EncryptionData']) - decoded_data = _decode_base64_to_bytes(message['EncryptedMessageContents']) + decoded_data = decode_base64_to_bytes(message['EncryptedMessageContents']) except (KeyError, ValueError): # Message was not json formatted and so was not encrypted # or the user provided a json formatted message. @@ -501,51 +540,9 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption return message try: - return _decrypt(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') + return _decrypt_message(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') except Exception as error: raise HttpResponseError( message="Decryption failed.", response=response, error=error) - - -def _decrypt(message, encryption_data, key_encryption_key=None, resolver=None): - ''' - Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. - Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). - Returns the original plaintex. - - :param str message: - The ciphertext to be decrypted. - :param _EncryptionData encryption_data: - The metadata associated with this ciphertext. - :param object key_encryption_key: - The user-provided key-encryption-key. Must implement the following methods: - unwrap_key(key, algorithm) - - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. - get_kid() - - returns a string key id for this key-encryption-key. - :param function resolver(kid): - The user-provided key resolver. Uses the kid string to return a key-encryption-key - implementing the interface defined above. - :return: The decrypted plaintext. - :rtype: str - ''' - _validate_not_none('message', message) - content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) - - if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) - - cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) - - # decrypt data - decrypted_data = message - decryptor = cipher.decryptor() - decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) - - # unpad data - unpadder = PKCS7(128).unpadder() - decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) - - return decrypted_data 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 dbad2a1c58c8..30e4506254d6 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 @@ -138,6 +138,26 @@ class StorageErrorCode(str, Enum): queue_not_empty = "QueueNotEmpty" queue_not_found = "QueueNotFound" + # File values + cannot_delete_file_or_directory = "CannotDeleteFileOrDirectory" + client_cache_flush_delay = "ClientCacheFlushDelay" + delete_pending = "DeletePending" + directory_not_empty = "DirectoryNotEmpty" + file_lock_conflict = "FileLockConflict" + invalid_file_or_directory_path_name = "InvalidFileOrDirectoryPathName" + parent_not_found = "ParentNotFound" + read_only_attribute = "ReadOnlyAttribute" + share_already_exists = "ShareAlreadyExists" + share_being_deleted = "ShareBeingDeleted" + share_disabled = "ShareDisabled" + share_not_found = "ShareNotFound" + sharing_violation = "SharingViolation" + share_snapshot_in_progress = "ShareSnapshotInProgress" + share_snapshot_count_exceeded = "ShareSnapshotCountExceeded" + share_snapshot_operation_not_supported = "ShareSnapshotOperationNotSupported" + share_has_snapshots = "ShareHasSnapshots" + container_quota_downgrade_not_allowed = "ContainerQuotaDowngradeNotAllowed" + class DictMixin(object): diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py index fa9770a8e131..5b0212fd9090 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py @@ -104,25 +104,6 @@ def on_request(self, request, **kwargs): message_id) -class StorageBlobSettings(object): - - def __init__(self, **kwargs): - self.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) - self.copy_polling_interval = 15 - - # Block blob uploads - self.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) - self.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) - self.use_byte_buffer = kwargs.get('use_byte_buffer', False) - - # Page blob uploads - self.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) - - # Blob downloads - self.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) - self.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) - - class StorageHeadersPolicy(HeadersPolicy): def on_request(self, request, **kwargs): @@ -253,7 +234,9 @@ class StorageUserAgentPolicy(SansIOHTTPPolicy): def __init__(self, **kwargs): self._application = kwargs.pop('user_agent', None) - self._user_agent = "azsdk-python-storage-blob/{} Python/{} ({})".format( + storage_sdk = kwargs.pop('storage_sdk') + self._user_agent = "azsdk-python-storage-{}/{} Python/{} ({})".format( + storage_sdk, VERSION, platform.python_version(), platform.platform()) diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/request_handlers.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/request_handlers.py new file mode 100644 index 000000000000..cd5e4848633d --- /dev/null +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/request_handlers.py @@ -0,0 +1,144 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) + +import logging +from os import fstat +from io import (SEEK_END, SEEK_SET, UnsupportedOperation) + +import isodate + +from azure.core import Configuration +from azure.core.exceptions import raise_with_traceback +from azure.core.pipeline import Pipeline + + +_LOGGER = logging.getLogger(__name__) + + +def serialize_iso(attr): + """Serialize Datetime object into ISO-8601 formatted string. + + :param Datetime attr: Object to be serialized. + :rtype: str + :raises: ValueError if format invalid. + """ + if not attr: + return None + if isinstance(attr, str): + attr = isodate.parse_datetime(attr) + try: + utc = attr.utctimetuple() + if utc.tm_year > 9999 or utc.tm_year < 1: + raise OverflowError("Hit max or min date") + + date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( + utc.tm_year, utc.tm_mon, utc.tm_mday, + utc.tm_hour, utc.tm_min, utc.tm_sec) + return date + 'Z' + except (ValueError, OverflowError) as err: + msg = "Unable to serialize datetime object." + raise_with_traceback(ValueError, msg, err) + except AttributeError as err: + msg = "ISO-8601 object must be valid Datetime object." + raise_with_traceback(TypeError, msg, err) + + +def get_length(data): + length = None + # Check if object implements the __len__ method, covers most input cases such as bytearray. + try: + length = len(data) + except: # pylint: disable=bare-except + pass + + if not length: + # Check if the stream is a file-like stream object. + # If so, calculate the size using the file descriptor. + try: + fileno = data.fileno() + except (AttributeError, UnsupportedOperation): + pass + else: + return fstat(fileno).st_size + + # If the stream is seekable and tell() is implemented, calculate the stream size. + try: + current_position = data.tell() + data.seek(0, SEEK_END) + length = data.tell() - current_position + data.seek(current_position, SEEK_SET) + except (AttributeError, UnsupportedOperation): + pass + + return length + + +def read_length(data): + try: + if hasattr(data, 'read'): + read_data = b'' + for chunk in iter(lambda: data.read(4096), b""): + read_data += chunk + return len(read_data), read_data + if hasattr(data, '__iter__'): + read_data = b'' + for chunk in data: + read_data += chunk + return len(read_data), read_data + except: # pylint: disable=bare-except + pass + raise ValueError("Unable to calculate content length, please specify.") + + +def validate_and_format_range_headers( + start_range, end_range, start_range_required=True, + end_range_required=True, check_content_md5=False, align_to_page=False): + # If end range is provided, start range must be provided + if (start_range_required or end_range is not None) and start_range is None: + raise ValueError("start_range value cannot be None.") + if end_range_required and end_range is None: + raise ValueError("end_range value cannot be None.") + + # Page ranges must be 512 aligned + if align_to_page: + if start_range is not None and start_range % 512 != 0: + raise ValueError("Invalid page blob start_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(start_range)) + if end_range is not None and end_range % 512 != 511: + raise ValueError("Invalid page blob end_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(end_range)) + + # Format based on whether end_range is present + range_header = None + if end_range is not None: + range_header = 'bytes={0}-{1}'.format(start_range, end_range) + elif start_range is not None: + range_header = "bytes={0}-".format(start_range) + + # Content MD5 can only be provided for a complete range less than 4MB in size + range_validation = None + if check_content_md5: + if start_range is None or end_range is None: + raise ValueError("Both start and end range requied for MD5 content validation.") + if end_range - start_range > 4 * 1024 * 1024: + raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") + range_validation = 'true' + + return range_header, range_validation + + +def add_metadata_headers(metadata=None): + # type: (Optional[Dict[str, str]]) -> Dict[str, str] + headers = {} + if metadata: + for key, value in metadata.items(): + headers['x-ms-meta-{}'.format(key)] = value + return headers 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 new file mode 100644 index 000000000000..472399264aa9 --- /dev/null +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/response_handlers.py @@ -0,0 +1,132 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging + +from azure.core.pipeline.policies import ContentDecodePolicy +from azure.core.exceptions import ( + HttpResponseError, + ResourceNotFoundError, + ResourceModifiedError, + ResourceExistsError, + ClientAuthenticationError, + DecodeError) + +from .models import StorageErrorCode + + +if TYPE_CHECKING: + from datetime import datetime + from azure.core.exceptions import AzureError + + +_LOGGER = logging.getLogger(__name__) + + +def parse_length_from_content_range(content_range): + ''' + Parses the blob length from the content range header: bytes 1-3/65537 + ''' + if content_range is None: + return None + + # First, split in space and take the second half: '1-3/65537' + # Next, split on slash and take the second half: '65537' + # Finally, convert to an int: 65537 + return int(content_range.split(' ', 1)[1].split('/', 1)[1]) + + +def normalize_headers(headers): + normalized = {} + for key, value in headers.items(): + if key.startswith('x-ms-'): + key = key[5:] + normalized[key.lower().replace('-', '_')] = value + return normalized + + +def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argument + raw_metadata = {k: v for k, v in response.headers.items() if k.startswith("x-ms-meta-")} + return {k[10:]: v for k, v in raw_metadata.items()} + + +def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers) + + +def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers), deserialized + + +def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return response.location_mode, deserialized + + +def process_storage_error(storage_error): + raise_error = HttpResponseError + error_code = storage_error.response.headers.get('x-ms-error-code') + error_message = storage_error.message + additional_data = {} + try: + error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) + if error_body: + for info in error_body.iter(): + if info.tag.lower() == 'code': + error_code = info.text + elif info.tag.lower() == 'message': + error_message = info.text + else: + additional_data[info.tag] = info.text + except DecodeError: + pass + + try: + if error_code: + error_code = StorageErrorCode(error_code) + if error_code in [StorageErrorCode.condition_not_met, + StorageErrorCode.blob_overwritten]: + raise_error = ResourceModifiedError + if error_code in [StorageErrorCode.invalid_authentication_info, + StorageErrorCode.authentication_failed]: + raise_error = ClientAuthenticationError + if error_code in [StorageErrorCode.resource_not_found, + StorageErrorCode.blob_not_found, + StorageErrorCode.queue_not_found, + StorageErrorCode.container_not_found, + StorageErrorCode.parent_not_found, + StorageErrorCode.share_not_found]: + raise_error = ResourceNotFoundError + if error_code in [StorageErrorCode.account_already_exists, + StorageErrorCode.account_being_created, + StorageErrorCode.resource_already_exists, + StorageErrorCode.resource_type_mismatch, + StorageErrorCode.blob_already_exists, + StorageErrorCode.queue_already_exists, + StorageErrorCode.container_already_exists, + StorageErrorCode.container_being_deleted, + StorageErrorCode.queue_being_deleted, + StorageErrorCode.share_already_exists, + StorageErrorCode.share_being_deleted]: + raise_error = ResourceExistsError + except ValueError: + # Got an unknown error code + pass + + try: + error_message += "\nErrorCode:{}".format(error_code.value) + except AttributeError: + error_message += "\nErrorCode:{}".format(error_code) + for name, info in additional_data.items(): + error_message += "\n{}:{}".format(name, info) + + error = raise_error(message=error_message, response=storage_error.response) + error.error_code = error_code + error.additional_info = additional_data + raise error diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py index cad3f270600b..16ff778c5c1e 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 @@ -8,7 +8,7 @@ from datetime import date from .constants import X_MS_VERSION -from .utils import _sign_string, url_quote, _QueryStringConstants +from . import sign_string, url_quote if sys.version_info < (3,): @@ -25,6 +25,54 @@ def _to_utc_datetime(value): return value.strftime('%Y-%m-%dT%H:%M:%SZ') +class QueryStringConstants(object): + SIGNED_SIGNATURE = 'sig' + SIGNED_PERMISSION = 'sp' + SIGNED_START = 'st' + SIGNED_EXPIRY = 'se' + SIGNED_RESOURCE = 'sr' + SIGNED_IDENTIFIER = 'si' + SIGNED_IP = 'sip' + SIGNED_PROTOCOL = 'spr' + SIGNED_VERSION = 'sv' + SIGNED_CACHE_CONTROL = 'rscc' + SIGNED_CONTENT_DISPOSITION = 'rscd' + SIGNED_CONTENT_ENCODING = 'rsce' + SIGNED_CONTENT_LANGUAGE = 'rscl' + SIGNED_CONTENT_TYPE = 'rsct' + START_PK = 'spk' + START_RK = 'srk' + END_PK = 'epk' + END_RK = 'erk' + SIGNED_RESOURCE_TYPES = 'srt' + SIGNED_SERVICES = 'ss' + + @staticmethod + def to_list(): + return [ + QueryStringConstants.SIGNED_SIGNATURE, + QueryStringConstants.SIGNED_PERMISSION, + QueryStringConstants.SIGNED_START, + QueryStringConstants.SIGNED_EXPIRY, + QueryStringConstants.SIGNED_RESOURCE, + QueryStringConstants.SIGNED_IDENTIFIER, + QueryStringConstants.SIGNED_IP, + QueryStringConstants.SIGNED_PROTOCOL, + QueryStringConstants.SIGNED_VERSION, + QueryStringConstants.SIGNED_CACHE_CONTROL, + QueryStringConstants.SIGNED_CONTENT_DISPOSITION, + QueryStringConstants.SIGNED_CONTENT_ENCODING, + QueryStringConstants.SIGNED_CONTENT_LANGUAGE, + QueryStringConstants.SIGNED_CONTENT_TYPE, + QueryStringConstants.START_PK, + QueryStringConstants.START_RK, + QueryStringConstants.END_PK, + QueryStringConstants.END_RK, + QueryStringConstants.SIGNED_RESOURCE_TYPES, + QueryStringConstants.SIGNED_SERVICES, + ] + + class SharedAccessSignature(object): ''' Provides a factory for creating account access @@ -112,33 +160,33 @@ def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): 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) + 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) + self._add_query(QueryStringConstants.SIGNED_RESOURCE, resource) def add_id(self, policy_id): - self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, 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) + 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) + 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): @@ -153,29 +201,29 @@ def get_value_to_append(query): # 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) + + (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_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)) + (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)) + 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): @@ -184,17 +232,17 @@ def get_value_to_append(query): 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)) + 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]) @@ -451,18 +499,193 @@ def get_value_to_append(query): # 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) + + (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_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)) + 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-blob/azure/storage/blob/_shared/upload_chunking.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/upload_chunking.py index 775c56853eac..86a3f4224ffa 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/upload_chunking.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/upload_chunking.py @@ -13,23 +13,45 @@ import six from .models import ModifiedAccessConditions -from .utils import ( - encode_base64, - url_quote, - get_length, - return_response_headers) -from .encryption import _get_blob_encryptor_and_padder +from . import encode_base64, url_quote +from .request_handlers import get_length +from .response_handlers import return_response_headers +from .encryption import get_blob_encryptor_and_padder _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024 _ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.' +def upload_file_chunks(file_service, file_size, block_size, stream, max_connections, + validate_content, timeout, **kwargs): + uploader = FileChunkUploader( + file_service, + file_size, + block_size, + stream, + max_connections > 1, + validate_content, + timeout, + **kwargs + ) + if max_connections > 1: + import concurrent.futures + executor = concurrent.futures.ThreadPoolExecutor(max_connections) + range_ids = list(executor.map(uploader.process_chunk, uploader.get_chunk_offsets())) + else: + if file_size is not None: + range_ids = [uploader.process_chunk(start) for start in uploader.get_chunk_offsets()] + else: + range_ids = uploader.process_all_unknown_size() + return range_ids + + def upload_blob_chunks(blob_service, blob_size, block_size, stream, max_connections, validate_content, # pylint: disable=too-many-locals access_conditions, uploader_class, append_conditions=None, modified_access_conditions=None, timeout=None, content_encryption_key=None, initialization_vector=None, **kwargs): - encryptor, padder = _get_blob_encryptor_and_padder( + encryptor, padder = get_blob_encryptor_and_padder( content_encryption_key, initialization_vector, uploader_class is not PageBlobChunkUploader) @@ -353,6 +375,94 @@ def _upload_chunk(self, chunk_offset, chunk_data): ) +class FileChunkUploader(object): # pylint: disable=too-many-instance-attributes + + def __init__(self, file_service, file_size, chunk_size, stream, parallel, + validate_content, timeout, **kwargs): + self.file_service = file_service + self.file_size = file_size + self.chunk_size = chunk_size + self.stream = stream + self.parallel = parallel + self.stream_start = stream.tell() if parallel else None + self.stream_lock = Lock() if parallel else None + self.progress_total = 0 + self.progress_lock = Lock() if parallel else None + self.validate_content = validate_content + self.timeout = timeout + self.request_options = kwargs + + def get_chunk_offsets(self): + index = 0 + if self.file_size is None: + # we don't know the size of the stream, so we have no + # choice but to seek + while True: + data = self._read_from_stream(index, 1) + if not data: + break + yield index + index += self.chunk_size + else: + while index < self.file_size: + yield index + index += self.chunk_size + + def process_chunk(self, chunk_offset): + size = self.chunk_size + if self.file_size is not None: + size = min(size, self.file_size - chunk_offset) + chunk_data = self._read_from_stream(chunk_offset, size) + return self._upload_chunk_with_progress(chunk_offset, chunk_data) + + def process_all_unknown_size(self): + assert self.stream_lock is None + range_ids = [] + index = 0 + while True: + data = self._read_from_stream(None, self.chunk_size) + if data: + index += len(data) + range_id = self._upload_chunk_with_progress(index, data) + range_ids.append(range_id) + else: + break + + return range_ids + + def _read_from_stream(self, offset, count): + if self.stream_lock is not None: + with self.stream_lock: + self.stream.seek(self.stream_start + offset) + data = self.stream.read(count) + else: + data = self.stream.read(count) + return data + + def _update_progress(self, length): + if self.progress_lock is not None: + with self.progress_lock: + self.progress_total += length + else: + self.progress_total += length + + def _upload_chunk_with_progress(self, chunk_start, chunk_data): + chunk_end = chunk_start + len(chunk_data) - 1 + self.file_service.upload_range( + chunk_data, + chunk_start, + chunk_end, + validate_content=self.validate_content, + timeout=self.timeout, + data_stream_total=self.file_size, + upload_stream_current=self.progress_total, + **self.request_options + ) + range_id = 'bytes={0}-{1}'.format(chunk_start, chunk_end) + self._update_progress(len(chunk_data)) + return range_id + + class _SubStream(IOBase): def __init__(self, wrapped_stream, stream_begin_index, length, lockObj): # Python 2.7: file-like objects created with open() typically support seek(), but are not diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py deleted file mode 100644 index 6d980d967891..000000000000 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/utils.py +++ /dev/null @@ -1,606 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - -from typing import ( # pylint: disable=unused-import - Union, Optional, Any, Iterable, Dict, List, Type, Tuple, - TYPE_CHECKING -) -import base64 -import hashlib -import hmac -import logging -from os import fstat -from io import (SEEK_END, SEEK_SET, UnsupportedOperation) - -try: - from urllib.parse import quote, unquote, parse_qs -except ImportError: - from urlparse import parse_qs # type: ignore - from urllib2 import quote, unquote # type: ignore - -import six -import isodate - -from azure.core import Configuration -from azure.core.exceptions import raise_with_traceback -from azure.core.pipeline import Pipeline -from azure.core.pipeline.transport import RequestsTransport -from azure.core.pipeline.policies import ( - RedirectPolicy, - ContentDecodePolicy, - BearerTokenCredentialPolicy, - ProxyPolicy) -from azure.core.exceptions import ( - HttpResponseError, - ResourceNotFoundError, - ResourceModifiedError, - ResourceExistsError, - ClientAuthenticationError, - DecodeError) - -from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT -from .models import LocationMode, StorageErrorCode -from .authentication import SharedKeyCredentialPolicy -from .policies import ( - StorageBlobSettings, - StorageHeadersPolicy, - StorageUserAgentPolicy, - StorageContentValidation, - StorageRequestHook, - StorageResponseHook, - StorageLoggingPolicy, - StorageHosts, - QueueMessagePolicy, - ExponentialRetry) - - -if TYPE_CHECKING: - from datetime import datetime - from azure.core.pipeline.transport import HttpTransport - from azure.core.pipeline.policies import HTTPPolicy - from azure.core.exceptions import AzureError - - -_LOGGER = logging.getLogger(__name__) - - -class _QueryStringConstants(object): - SIGNED_SIGNATURE = 'sig' - SIGNED_PERMISSION = 'sp' - SIGNED_START = 'st' - SIGNED_EXPIRY = 'se' - SIGNED_RESOURCE = 'sr' - SIGNED_IDENTIFIER = 'si' - SIGNED_IP = 'sip' - SIGNED_PROTOCOL = 'spr' - SIGNED_VERSION = 'sv' - SIGNED_CACHE_CONTROL = 'rscc' - SIGNED_CONTENT_DISPOSITION = 'rscd' - SIGNED_CONTENT_ENCODING = 'rsce' - SIGNED_CONTENT_LANGUAGE = 'rscl' - SIGNED_CONTENT_TYPE = 'rsct' - START_PK = 'spk' - START_RK = 'srk' - END_PK = 'epk' - END_RK = 'erk' - SIGNED_RESOURCE_TYPES = 'srt' - SIGNED_SERVICES = 'ss' - - @staticmethod - def to_list(): - return [ - _QueryStringConstants.SIGNED_SIGNATURE, - _QueryStringConstants.SIGNED_PERMISSION, - _QueryStringConstants.SIGNED_START, - _QueryStringConstants.SIGNED_EXPIRY, - _QueryStringConstants.SIGNED_RESOURCE, - _QueryStringConstants.SIGNED_IDENTIFIER, - _QueryStringConstants.SIGNED_IP, - _QueryStringConstants.SIGNED_PROTOCOL, - _QueryStringConstants.SIGNED_VERSION, - _QueryStringConstants.SIGNED_CACHE_CONTROL, - _QueryStringConstants.SIGNED_CONTENT_DISPOSITION, - _QueryStringConstants.SIGNED_CONTENT_ENCODING, - _QueryStringConstants.SIGNED_CONTENT_LANGUAGE, - _QueryStringConstants.SIGNED_CONTENT_TYPE, - _QueryStringConstants.START_PK, - _QueryStringConstants.START_RK, - _QueryStringConstants.END_PK, - _QueryStringConstants.END_RK, - _QueryStringConstants.SIGNED_RESOURCE_TYPES, - _QueryStringConstants.SIGNED_SERVICES, - ] - - -class StorageAccountHostsMixin(object): - - def __init__( - self, parsed_url, # type: Any - service, # type: str - credential=None, # type: Optional[Any] - **kwargs # type: Any - ): - # type: (...) -> None - self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) - self._hosts = kwargs.get('_hosts') - self.scheme = parsed_url.scheme - - if service not in ['blob', 'queue', 'file']: - raise ValueError("Invalid service: {}".format(service)) - account = parsed_url.netloc.split(".{}.core.".format(service)) - secondary_hostname = None - self.credential = format_shared_key_credential(account, credential) - if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): - raise ValueError("Token credential is only supported with HTTPS.") - if hasattr(self.credential, 'account_name'): - secondary_hostname = "{}-secondary.{}.{}".format( - self.credential.account_name, service, SERVICE_HOST_BASE) - - if not self._hosts: - if len(account) > 1: - secondary_hostname = parsed_url.netloc.replace( - account[0], - account[0] + '-secondary') - if kwargs.get('secondary_hostname'): - secondary_hostname = kwargs['secondary_hostname'] - self._hosts = { - LocationMode.PRIMARY: parsed_url.netloc, - LocationMode.SECONDARY: secondary_hostname} - - self.require_encryption = kwargs.get('require_encryption', False) - self.key_encryption_key = kwargs.get('key_encryption_key') - self.key_resolver_function = kwargs.get('key_resolver_function') - - self._config, self._pipeline = create_pipeline(self.credential, hosts=self._hosts, **kwargs) - - def __enter__(self): - self._client.__enter__() - return self - - def __exit__(self, *args): - self._client.__exit__(*args) - - @property - def url(self): - return self._format_url(self._hosts[self._location_mode]) - - @property - def primary_endpoint(self): - return self._format_url(self._hosts[LocationMode.PRIMARY]) - - @property - def primary_hostname(self): - return self._hosts[LocationMode.PRIMARY] - - @property - def secondary_endpoint(self): - if not self._hosts[LocationMode.SECONDARY]: - raise ValueError("No secondary host configured.") - return self._format_url(self._hosts[LocationMode.SECONDARY]) - - @property - def secondary_hostname(self): - return self._hosts[LocationMode.SECONDARY] - - @property - def location_mode(self): - return self._location_mode - - @location_mode.setter - def location_mode(self, value): - if self._hosts.get(value): - self._location_mode = value - self._client._config.url = self.url # pylint: disable=protected-access - else: - raise ValueError("No host URL for location mode: {}".format(value)) - - def _format_query_string(self, sas_token, credential, snapshot=None): - query_str = "?" - if snapshot: - query_str += 'snapshot={}&'.format(self.snapshot) - if sas_token and not credential: - query_str += sas_token - elif is_credential_sastoken(credential): - query_str += credential.lstrip('?') - credential = None - return query_str.rstrip('?&'), credential - - -def format_shared_key_credential(account, credential): - if isinstance(credential, six.string_types): - if len(account) < 2: - raise ValueError("Unable to determine account name for shared key credential.") - credential = { - 'account_name': account[0], - 'account_key': credential - } - if isinstance(credential, dict): - if 'account_name' not in credential: - raise ValueError("Shared key credential missing 'account_name") - if 'account_key' not in credential: - raise ValueError("Shared key credential missing 'account_key") - return SharedKeyCredentialPolicy(**credential) - return credential - - -service_connection_params = { - 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, - 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, - 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, -} - - -def parse_connection_str(conn_str, credential, service): - conn_str = conn_str.rstrip(';') - conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension - endpoints = service_connection_params[service] - primary = None - secondary = None - if not credential: - try: - credential = { - 'account_name': conn_settings['AccountName'], - 'account_key': conn_settings['AccountKey'] - } - except KeyError: - credential = conn_settings.get('SharedAccessSignature') - if endpoints['primary'] in conn_settings: - primary = conn_settings[endpoints['primary']] - if endpoints['secondary'] in conn_settings: - secondary = conn_settings[endpoints['secondary']] - else: - if endpoints['secondary'] in conn_settings: - raise ValueError("Connection string specifies only secondary endpoint.") - try: - primary = "{}://{}.{}.{}".format( - conn_settings['DefaultEndpointsProtocol'], - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - secondary = "{}-secondary.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - except KeyError: - pass - - if not primary: - try: - primary = "https://{}.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) - ) - except KeyError: - raise ValueError("Connection string missing required connection details.") - return primary, secondary, credential - - -def url_quote(url): - return quote(url) - - -def url_unquote(url): - return unquote(url) - - -def encode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') - - -def decode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - decoded = base64.b64decode(data) - return decoded.decode('utf-8') - - -def _decode_base64_to_bytes(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - return base64.b64decode(data) - - -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, six.text_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, six.text_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = encode_base64(digest) - return encoded_digest - - -def serialize_iso(attr): - """Serialize Datetime object into ISO-8601 formatted string. - - :param Datetime attr: Object to be serialized. - :rtype: str - :raises: ValueError if format invalid. - """ - if not attr: - return None - if isinstance(attr, str): - attr = isodate.parse_datetime(attr) - try: - utc = attr.utctimetuple() - if utc.tm_year > 9999 or utc.tm_year < 1: - raise OverflowError("Hit max or min date") - - date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( - utc.tm_year, utc.tm_mon, utc.tm_mday, - utc.tm_hour, utc.tm_min, utc.tm_sec) - return date + 'Z' - except (ValueError, OverflowError) as err: - msg = "Unable to serialize datetime object." - raise_with_traceback(ValueError, msg, err) - except AttributeError as err: - msg = "ISO-8601 object must be valid Datetime object." - raise_with_traceback(TypeError, msg, err) - - -def get_length(data): - length = None - # Check if object implements the __len__ method, covers most input cases such as bytearray. - try: - length = len(data) - except: # pylint: disable=bare-except - pass - - if not length: - # Check if the stream is a file-like stream object. - # If so, calculate the size using the file descriptor. - try: - fileno = data.fileno() - except (AttributeError, UnsupportedOperation): - pass - else: - return fstat(fileno).st_size - - # If the stream is seekable and tell() is implemented, calculate the stream size. - try: - current_position = data.tell() - data.seek(0, SEEK_END) - length = data.tell() - current_position - data.seek(current_position, SEEK_SET) - except (AttributeError, UnsupportedOperation): - pass - - return length - - -def read_length(data): - try: - if hasattr(data, 'read'): - read_data = b'' - for chunk in iter(lambda: data.read(4096), b""): - read_data += chunk - return len(read_data), read_data - if hasattr(data, '__iter__'): - read_data = b'' - for chunk in data: - read_data += chunk - return len(read_data), read_data - except: # pylint: disable=bare-except - pass - raise ValueError("Unable to calculate content length, please specify.") - - -def parse_length_from_content_range(content_range): - ''' - Parses the blob length from the content range header: bytes 1-3/65537 - ''' - if content_range is None: - return None - - # First, split in space and take the second half: '1-3/65537' - # Next, split on slash and take the second half: '65537' - # Finally, convert to an int: 65537 - return int(content_range.split(' ', 1)[1].split('/', 1)[1]) - - -def validate_and_format_range_headers( - start_range, end_range, start_range_required=True, - end_range_required=True, check_content_md5=False, align_to_page=False): - # If end range is provided, start range must be provided - if (start_range_required or end_range is not None) and start_range is None: - raise ValueError("start_range value cannot be None.") - if end_range_required and end_range is None: - raise ValueError("end_range value cannot be None.") - - # Page ranges must be 512 aligned - if align_to_page: - if start_range is not None and start_range % 512 != 0: - raise ValueError("Invalid page blob start_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(start_range)) - if end_range is not None and end_range % 512 != 511: - raise ValueError("Invalid page blob end_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(end_range)) - - # Format based on whether end_range is present - range_header = None - if end_range is not None: - range_header = 'bytes={0}-{1}'.format(start_range, end_range) - elif start_range is not None: - range_header = "bytes={0}-".format(start_range) - - # Content MD5 can only be provided for a complete range less than 4MB in size - range_validation = None - if check_content_md5: - if start_range is None or end_range is None: - raise ValueError("Both start and end range requied for MD5 content validation.") - if end_range - start_range > 4 * 1024 * 1024: - raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") - range_validation = 'true' - - return range_header, range_validation - - -def normalize_headers(headers): - normalized = {} - for key, value in headers.items(): - if key.startswith('x-ms-'): - key = key[5:] - normalized[key.lower().replace('-', '_')] = value - return normalized - - -def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers) - - -def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers), deserialized - - -def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return response.location_mode, deserialized - - -def create_configuration(**kwargs): - # type: (**Any) -> Configuration - if 'connection_timeout' not in kwargs: - kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT - config = Configuration(**kwargs) - config.headers_policy = StorageHeadersPolicy(**kwargs) - config.user_agent_policy = StorageUserAgentPolicy(**kwargs) - config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) - config.redirect_policy = RedirectPolicy(**kwargs) - config.logging_policy = StorageLoggingPolicy(**kwargs) - config.proxy_policy = ProxyPolicy(**kwargs) - config.blob_settings = StorageBlobSettings(**kwargs) - return config - - -def create_pipeline(credential, **kwargs): - # type: (Any, **Any) -> Tuple[Configuration, Pipeline] - credential_policy = None - if hasattr(credential, 'get_token'): - credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) - elif isinstance(credential, SharedKeyCredentialPolicy): - credential_policy = credential - elif credential is not None: - raise TypeError("Unsupported credential: {}".format(credential)) - - config = kwargs.get('_configuration') or create_configuration(**kwargs) - if kwargs.get('_pipeline'): - return config, kwargs['_pipeline'] - transport = kwargs.get('transport') # type: HttpTransport - if not transport: - transport = RequestsTransport(config) - policies = [ - QueueMessagePolicy(), - config.headers_policy, - config.user_agent_policy, - StorageContentValidation(), - StorageRequestHook(**kwargs), - credential_policy, - ContentDecodePolicy(), - config.redirect_policy, - StorageHosts(**kwargs), - config.retry_policy, - config.logging_policy, - StorageResponseHook(**kwargs), - ] - return config, Pipeline(transport, policies=policies) - - -def parse_query(query_str): - sas_values = _QueryStringConstants.to_list() - parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} - sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] - sas_token = None - if sas_params: - sas_token = '&'.join(sas_params) - - return parsed_query.get('snapshot'), sas_token - - -def is_credential_sastoken(credential): - if not credential or not isinstance(credential, six.string_types): - return False - - sas_values = _QueryStringConstants.to_list() - parsed_query = parse_qs(credential.lstrip('?')) - if parsed_query and all([k in sas_values for k in parsed_query.keys()]): - return True - return False - - -def add_metadata_headers(metadata): - headers = {} - if metadata: - for key, value in metadata.items(): - headers['x-ms-meta-{}'.format(key)] = value - return headers - - -def process_storage_error(storage_error): - raise_error = HttpResponseError - error_code = storage_error.response.headers.get('x-ms-error-code') - error_message = storage_error.message - additional_data = {} - try: - error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) - if error_body: - for info in error_body.iter(): - if info.tag.lower() == 'code': - error_code = info.text - elif info.tag.lower() == 'message': - error_message = info.text - else: - additional_data[info.tag] = info.text - except DecodeError: - pass - - try: - if error_code: - error_code = StorageErrorCode(error_code) - if error_code in [StorageErrorCode.condition_not_met, - StorageErrorCode.blob_overwritten]: - raise_error = ResourceModifiedError - if error_code in [StorageErrorCode.invalid_authentication_info, - StorageErrorCode.authentication_failed]: - raise_error = ClientAuthenticationError - if error_code in [StorageErrorCode.resource_not_found, - StorageErrorCode.blob_not_found, - StorageErrorCode.queue_not_found, - StorageErrorCode.container_not_found]: - raise_error = ResourceNotFoundError - if error_code in [StorageErrorCode.account_already_exists, - StorageErrorCode.account_being_created, - StorageErrorCode.resource_already_exists, - StorageErrorCode.resource_type_mismatch, - StorageErrorCode.blob_already_exists, - StorageErrorCode.queue_already_exists, - StorageErrorCode.container_already_exists, - StorageErrorCode.container_being_deleted, - StorageErrorCode.queue_being_deleted]: - raise_error = ResourceExistsError - except ValueError: - # Got an unknown error code - pass - - try: - error_message += "\nErrorCode:{}".format(error_code.value) - except AttributeError: - error_message += "\nErrorCode:{}".format(error_code) - for name, info in additional_data.items(): - error_message += "\n{}:{}".format(name, info) - - error = raise_error(message=error_message, response=storage_error.response) - error.error_code = error_code - error.additional_info = additional_data - raise error diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py index dd0018a17386..acc1c09ce2ec 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py @@ -18,33 +18,30 @@ import six +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.encryption import generate_blob_encryption_data from ._shared.upload_chunking import IterStreamer -from ._shared.utils import ( - StorageAccountHostsMixin, - return_response_headers, - add_metadata_headers, - encode_base64, - get_length, - read_length, - parse_connection_str, - parse_query, - process_storage_error, +from ._shared.download_chunking import StorageStreamDownloader +from ._shared.request_handlers import ( + add_metadata_headers, get_length, read_length, validate_and_format_range_headers) +from ._shared.response_handlers import return_response_headers, process_storage_error from ._generated import AzureBlobStorage from ._generated.models import ( DeleteSnapshotsOptionType, BlobHTTPHeaders, BlockLookupList, AppendPositionAccessConditions, + SourceModifiedAccessConditions, StorageErrorException) from ._blob_utils import ( deserialize_blob_properties, + deserialize_blob_stream, get_access_conditions, get_modification_conditions, get_sequence_conditions, - StorageStreamDownloader, upload_block_blob, upload_page_blob, upload_append_blob) @@ -439,7 +436,7 @@ def upload_blob( # pylint: disable=too-many-locals cek, iv, encryption_data = None, None, None if self.key_encryption_key is not None: - cek, iv, encryption_data = _generate_blob_encryption_data(self.key_encryption_key) + cek, iv, encryption_data = generate_blob_encryption_data(self.key_encryption_key) if isinstance(data, six.text_type): data = data.encode(encoding) # type: ignore @@ -486,7 +483,7 @@ def upload_blob( # pylint: disable=too-many-locals validate_content, timeout, max_connections, - self._config.blob_settings, + self._config, self.require_encryption, self.key_encryption_key, **kwargs) @@ -504,7 +501,7 @@ def upload_blob( # pylint: disable=too-many-locals premium_page_blob_tier, timeout, max_connections, - self._config.blob_settings, + self._config, cek, iv, encryption_data, @@ -525,7 +522,7 @@ def upload_blob( # pylint: disable=too-many-locals validate_content, timeout, max_connections, - self._config.blob_settings, + self._config, **kwargs) raise ValueError("Unsupported BlobType: {}".format(blob_type)) @@ -609,19 +606,23 @@ def download_blob( if_modified_since, if_unmodified_since, if_match, if_none_match) return StorageStreamDownloader( - name=self.blob_name, - container=self.container_name, service=self._client.blob, - config=self._config.blob_settings, + config=self._config, offset=offset, length=length, validate_content=validate_content, - access_conditions=access_conditions, - mod_conditions=mod_conditions, + encryption_options={ + 'required': self.require_encryption, + 'key': self.key_encryption_key, + 'resolver': self.key_resolver_function}, + extra_properties={ + 'name': self.blob_name, + 'container': self.container_name + }, + lease_access_conditions=access_conditions, + modified_access_conditions=mod_conditions, + cls=deserialize_blob_stream, timeout=timeout, - require_encryption=self.require_encryption, - key_encryption_key=self.key_encryption_key, - key_resolver_function=self.key_resolver_function, **kwargs) def delete_blob( @@ -1370,11 +1371,11 @@ def copy_blob_from_url( # pylint: disable=too-many-locals cls=return_response_headers, **kwargs) else: - source_mod_conditions = get_modification_conditions( - source_if_modified_since, - source_if_unmodified_since, - source_if_match, - source_if_none_match) + source_mod_conditions = SourceModifiedAccessConditions( + source_if_modified_since=source_if_modified_since, + source_if_unmodified_since=source_if_unmodified_since, + source_if_match=source_if_match, + source_if_none_match=source_if_none_match) start_copy = self._client.blob.start_copy_from_url( source_url, timeout=timeout, 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 c2b7cce9f255..fe5f9cd8c824 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 @@ -16,12 +16,8 @@ from ._shared.shared_access_signature import SharedAccessSignature from ._shared.models import LocationMode, Services -from ._shared.utils import ( - StorageAccountHostsMixin, - return_response_headers, - parse_connection_str, - process_storage_error, - parse_query) +from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query +from ._shared.response_handlers import return_response_headers, process_storage_error from ._generated import AzureBlobStorage from ._generated.models import StorageErrorException, StorageServiceProperties from .container_client import ContainerClient diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py b/sdk/storage/azure-storage-blob/azure/storage/blob/container_client.py index 28da09873c1e..82aaf9644cd9 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 @@ -19,14 +19,11 @@ import six from ._shared.shared_access_signature import BlobSharedAccessSignature -from ._shared.utils import ( - StorageAccountHostsMixin, +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, - add_metadata_headers, - parse_connection_str, - serialize_iso, - parse_query, return_headers_and_deserialized) from ._generated import AzureBlobStorage from ._generated.models import ( diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/lease.py b/sdk/storage/azure-storage-blob/azure/storage/blob/lease.py index 0b5096559ec3..1e3276aeb756 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/lease.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/lease.py @@ -11,7 +11,7 @@ TypeVar, TYPE_CHECKING ) -from ._shared.utils import return_response_headers, process_storage_error +from ._shared.response_handlers import return_response_headers, process_storage_error from ._generated.models import StorageErrorException from ._blob_utils import get_modification_conditions diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/models.py b/sdk/storage/azure-storage-blob/azure/storage/blob/models.py index ab75e1d7722d..e03ace31284b 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/models.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/models.py @@ -11,10 +11,8 @@ from azure.core.paging import Paged -from ._shared.utils import ( - decode_base64, - return_context_and_deserialized, - process_storage_error) +from ._shared import decode_base64_to_text +from ._shared.response_handlers import return_context_and_deserialized, process_storage_error from ._shared.models import DictMixin, get_enum_value from ._generated.models import Logging as GeneratedLogging from ._generated.models import Metrics as GeneratedMetrics @@ -805,7 +803,7 @@ def __init__(self, block_id=None, state=BlockState.Latest): @classmethod def _from_generated(cls, generated): block = cls() - block.id = decode_base64(generated.name) + block.id = decode_base64_to_text(generated.name) block.size = generated.size return block diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/polling.py b/sdk/storage/azure-storage-blob/azure/storage/blob/polling.py index 148cf7708c98..d2561af3a5b8 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/polling.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/polling.py @@ -9,7 +9,7 @@ from typing import Callable, Any # pylint: disable=unused-import from azure.core.polling import PollingMethod, LROPoller -from ._shared.utils import process_storage_error +from ._shared.response_handlers import process_storage_error from ._generated.models import StorageErrorException from ._blob_utils import deserialize_blob_properties @@ -22,7 +22,7 @@ class CopyStatusPoller(LROPoller): def __init__(self, client, copy_id, polling=True, configuration=None, **kwargs): if configuration: - polling_interval = configuration.blob_settings.copy_polling_interval + polling_interval = configuration.copy_polling_interval else: polling_interval = 2 polling_method = CopyBlobPolling if polling else CopyBlob diff --git a/sdk/storage/azure-storage-blob/dev_requirements.txt b/sdk/storage/azure-storage-blob/dev_requirements.txt index 78ea6cb44052..70bc75a7b9d7 100644 --- a/sdk/storage/azure-storage-blob/dev_requirements.txt +++ b/sdk/storage/azure-storage-blob/dev_requirements.txt @@ -1,5 +1,5 @@ -e ../../../tools/azure-sdk-tools -e ../../identity/azure-identity -pylint==2.1.1; python_version >= '3.4' +pylint==2.3.1; python_version >= '3.4' pylint==1.8.4; python_version < '3.4' aiohttp>=3.0; python_version >= '3.5' diff --git a/sdk/storage/azure-storage-blob/tests/test_append_blob.py b/sdk/storage/azure-storage-blob/tests/test_append_blob.py index 0577ce0c1dd6..485b345cacb7 100644 --- a/sdk/storage/azure-storage-blob/tests/test_append_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_append_blob.py @@ -290,7 +290,7 @@ def progress_gen(upload): # Assert self.assertBlobEqual(blob, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) @record def test_append_blob_from_bytes_with_index(self): @@ -341,7 +341,7 @@ def test_append_blob_from_bytes_with_progress_chunked_upload(self): progress = [] def progress_gen(upload): - n = self.config.blob_settings.max_block_size + n = self.config.max_block_size total = len(upload) current = 0 while upload: @@ -355,7 +355,7 @@ def progress_gen(upload): # Assert self.assertBlobEqual(blob, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) @record def test_append_blob_from_bytes_chunked_upload_with_index_and_count(self): @@ -402,7 +402,7 @@ def test_append_blob_from_path_with_progress_chunked_upload(self): progress = [] def progress_gen(upload): - n = self.config.blob_settings.max_block_size + n = self.config.max_block_size total = LARGE_BLOB_SIZE current = 0 while upload: @@ -419,7 +419,7 @@ def progress_gen(upload): # Assert self.assertBlobEqual(blob, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) @record def test_append_blob_from_stream_chunked_upload(self): @@ -577,7 +577,7 @@ def progress_gen(upload): blob.upload_blob(upload_data, encoding='utf-16', blob_type=BlobType.AppendBlob) # Assert - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) @record def test_append_blob_from_text_chunked_upload(self): diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_encryption.py b/sdk/storage/azure-storage-blob/tests/test_blob_encryption.py index 6bc19cd92723..7cb2650490e4 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_encryption.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_encryption.py @@ -170,7 +170,7 @@ def test_invalid_value_kek_wrap(self): @record def test_missing_attribute_kek_unwrap(self): - # Shared between all services in _decrypt_blob + # Shared between all services in decrypt_blob # Arrange self.bsc.require_encryption = True valid_key = KeyWrapper('key1') @@ -282,7 +282,7 @@ def test_put_blob_invalid_stream_type(self): self.bsc.require_encryption = True self.bsc.key_encryption_key = KeyWrapper('key1') small_stream = StringIO(u'small') - large_stream = StringIO(u'large' * self.config.blob_settings.max_single_put_size) + large_stream = StringIO(u'large' * self.config.max_single_put_size) blob_name = self._get_blob_reference(BlobType.BlockBlob) blob = self.bsc.get_blob_client(self.container_name, blob_name) @@ -306,7 +306,7 @@ def test_put_blob_chunking_required_mult_of_block_size(self): self.bsc.key_encryption_key = KeyWrapper('key1') self.bsc.require_encryption = True content = self.get_random_bytes( - self.config.blob_settings.max_single_put_size + self.config.blob_settings.max_block_size) + self.config.max_single_put_size + self.config.max_block_size) blob_name = self._get_blob_reference(BlobType.BlockBlob) blob = self.bsc.get_blob_client(self.container_name, blob_name) @@ -325,7 +325,7 @@ def test_put_blob_chunking_required_non_mult_of_block_size(self): # Arrange self.bsc.key_encryption_key = KeyWrapper('key1') self.bsc.require_encryption = True - content = urandom(self.config.blob_settings.max_single_put_size + 1) + content = urandom(self.config.max_single_put_size + 1) blob_name = self._get_blob_reference(BlobType.BlockBlob) blob = self.bsc.get_blob_client(self.container_name, blob_name) @@ -344,19 +344,19 @@ def test_put_blob_chunking_required_range_specified(self): # Arrange self.bsc.key_encryption_key = KeyWrapper('key1') self.bsc.require_encryption = True - content = self.get_random_bytes(self.config.blob_settings.max_single_put_size * 2) + content = self.get_random_bytes(self.config.max_single_put_size * 2) blob_name = self._get_blob_reference(BlobType.BlockBlob) blob = self.bsc.get_blob_client(self.container_name, blob_name) # Act blob.upload_blob( content, - length=self.config.blob_settings.max_single_put_size + 53, + length=self.config.max_single_put_size + 53, max_connections=3) blob_content = blob.download_blob().content_as_bytes(max_connections=3) # Assert - self.assertEqual(content[:self.config.blob_settings.max_single_put_size+53], blob_content) + self.assertEqual(content[:self.config.max_single_put_size+53], blob_content) @record def test_put_block_blob_single_shot(self): @@ -379,7 +379,7 @@ def test_put_blob_range(self): # Arrange self.bsc.require_encryption = True self.bsc.key_encryption_key = KeyWrapper('key1') - content = b'Random repeats' * self.config.blob_settings.max_single_put_size * 5 + content = b'Random repeats' * self.config.max_single_put_size * 5 # All page blob uploads call _upload_chunks, so this will test the ability # of that function to handle ranges even though it's a small blob @@ -389,12 +389,12 @@ def test_put_blob_range(self): # Act blob.upload_blob( content[2:], - length=self.config.blob_settings.max_single_put_size + 5, + length=self.config.max_single_put_size + 5, max_connections=1) blob_content = blob.download_blob().content_as_bytes(max_connections=1) # Assert - self.assertEqual(content[2:2 + self.config.blob_settings.max_single_put_size + 5], blob_content) + self.assertEqual(content[2:2 + self.config.max_single_put_size + 5], blob_content) @record def test_put_blob_empty(self): @@ -417,7 +417,7 @@ def test_put_blob_serial_upload_chunking(self): # Arrange self.bsc.key_encryption_key = KeyWrapper('key1') self.bsc.require_encryption = True - content = self.get_random_bytes(self.config.blob_settings.max_single_put_size + 1) + content = self.get_random_bytes(self.config.max_single_put_size + 1) blob_name = self._get_blob_reference(BlobType.BlockBlob) blob = self.bsc.get_blob_client(self.container_name, blob_name) diff --git a/sdk/storage/azure-storage-blob/tests/test_block_blob.py b/sdk/storage/azure-storage-blob/tests/test_block_blob.py index 8e6cc1576637..902da9bf2ab9 100644 --- a/sdk/storage/azure-storage-blob/tests/test_block_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_block_blob.py @@ -479,7 +479,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) self.assertEqual(props.etag, create_resp.get('etag')) self.assertEqual(props.last_modified, create_resp.get('last_modified')) @@ -610,7 +610,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) def test_create_blob_from_path_with_properties(self): # parallel tests introduce random order of requests, can only run live @@ -725,7 +725,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) def test_create_blob_from_stream_chunked_upload_with_count(self): # parallel tests introduce random order of requests, can only run live @@ -849,7 +849,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) def test_create_blob_from_text_chunked_upload(self): # parallel tests introduce random order of requests, can only run live diff --git a/sdk/storage/azure-storage-blob/tests/test_get_blob.py b/sdk/storage/azure-storage-blob/tests/test_get_blob.py index 8c0ddbdc2889..35e7f3fe185a 100644 --- a/sdk/storage/azure-storage-blob/tests/test_get_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_get_blob.py @@ -247,8 +247,8 @@ def callback(response): self.assertEqual(self.byte_data, content) self.assert_download_progress( len(self.byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -269,8 +269,8 @@ def callback(response): self.assertEqual(self.byte_data, content) self.assert_download_progress( len(self.byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -295,8 +295,8 @@ def callback(response): self.assertEqual(blob_data, content) self.assert_download_progress( len(blob_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) def test_get_blob_to_stream(self): @@ -343,8 +343,8 @@ def callback(response): self.assertEqual(self.byte_data, actual) self.assert_download_progress( len(self.byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -370,8 +370,8 @@ def callback(response): self.assertEqual(self.byte_data, actual) self.assert_download_progress( len(self.byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -402,8 +402,8 @@ def callback(response): self.assertEqual(blob_data, actual) self.assert_download_progress( len(blob_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) def test_ranged_get_blob_to_path(self): @@ -415,7 +415,7 @@ def test_ranged_get_blob_to_path(self): blob = self.bsc.get_blob_client(self.container_name, self.byte_blob) # Act - end_range = self.config.blob_settings.max_single_get_size + end_range = self.config.max_single_get_size with open(FILE_PATH, 'wb') as stream: downloader = blob.download_blob(offset=1, length=end_range) properties = downloader.download_to_stream(stream, max_connections=2) @@ -442,7 +442,7 @@ def callback(response): # Act start_range = 3 - end_range = self.config.blob_settings.max_single_get_size + 1024 + end_range = self.config.max_single_get_size + 1024 with open(FILE_PATH, 'wb') as stream: downloader = blob.download_blob(offset=start_range, length=end_range, raw_response_hook=callback) properties = downloader.download_to_stream(stream, max_connections=2) @@ -454,8 +454,8 @@ def callback(response): self.assertEqual(self.byte_data[start_range:end_range + 1], actual) self.assert_download_progress( end_range - start_range + 1, - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -497,14 +497,14 @@ def test_ranged_get_blob_to_path_invalid_range_parallel(self): return # Arrange - blob_size = self.config.blob_settings.max_single_get_size + 1 + blob_size = self.config.max_single_get_size + 1 blob_data = self.get_random_bytes(blob_size) blob_name = self._get_blob_reference() blob = self.bsc.get_blob_client(self.container_name, blob_name) blob.upload_blob(blob_data) # Act - end_range = 2 * self.config.blob_settings.max_single_get_size + end_range = 2 * self.config.max_single_get_size with open(FILE_PATH, 'wb') as stream: downloader = blob.download_blob(offset=1, length=end_range) properties = downloader.download_to_stream(stream, max_connections=2) @@ -529,7 +529,7 @@ def test_ranged_get_blob_to_path_invalid_range_non_parallel(self): blob.upload_blob(blob_data) # Act - end_range = 2 * self.config.blob_settings.max_single_get_size + end_range = 2 * self.config.max_single_get_size with open(FILE_PATH, 'wb') as stream: downloader = blob.download_blob(offset=1, length=end_range) properties = downloader.download_to_stream(stream, max_connections=2) @@ -549,7 +549,7 @@ def test_get_blob_to_text(self): # Arrange text_blob = self.get_resource_name('textblob') - text_data = self.get_random_text_data(self.config.blob_settings.max_single_get_size + 1) + text_data = self.get_random_text_data(self.config.max_single_get_size + 1) blob = self.bsc.get_blob_client(self.container_name, text_blob) blob.upload_blob(text_data) @@ -566,7 +566,7 @@ def test_get_blob_to_text_with_progress(self): # Arrange text_blob = self.get_resource_name('textblob') - text_data = self.get_random_text_data(self.config.blob_settings.max_single_get_size + 1) + text_data = self.get_random_text_data(self.config.max_single_get_size + 1) blob = self.bsc.get_blob_client(self.container_name, text_blob) blob.upload_blob(text_data) @@ -584,15 +584,15 @@ def callback(response): self.assertEqual(text_data, content) self.assert_download_progress( len(text_data.encode('utf-8')), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record def test_get_blob_to_text_non_parallel(self): # Arrange text_blob = self._get_blob_reference() - text_data = self.get_random_text_data(self.config.blob_settings.max_single_get_size + 1) + text_data = self.get_random_text_data(self.config.max_single_get_size + 1) blob = self.bsc.get_blob_client(self.container_name, text_blob) blob.upload_blob(text_data) @@ -610,8 +610,8 @@ def callback(response): self.assertEqual(text_data, content) self.assert_download_progress( len(text_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -636,8 +636,8 @@ def callback(response): self.assertEqual(blob_data, content) self.assert_download_progress( len(blob_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -676,8 +676,8 @@ def callback(response): self.assertEqual(text, content) self.assert_download_progress( len(text.encode('utf-8')), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record @@ -717,7 +717,7 @@ def test_get_blob_non_seekable_parallel(self): def test_get_blob_to_stream_exact_get_size(self): # Arrange blob_name = self._get_blob_reference() - byte_data = self.get_random_bytes(self.config.blob_settings.max_single_get_size) + byte_data = self.get_random_bytes(self.config.max_single_get_size) blob = self.bsc.get_blob_client(self.container_name, blob_name) blob.upload_blob(byte_data) @@ -739,15 +739,15 @@ def callback(response): self.assertEqual(byte_data, actual) self.assert_download_progress( len(byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) @record def test_get_blob_exact_get_size(self): # Arrange blob_name = self._get_blob_reference() - byte_data = self.get_random_bytes(self.config.blob_settings.max_single_get_size) + byte_data = self.get_random_bytes(self.config.max_single_get_size) blob = self.bsc.get_blob_client(self.container_name, blob_name) blob.upload_blob(byte_data) @@ -765,8 +765,8 @@ def callback(response): self.assertEqual(byte_data, content) self.assert_download_progress( len(byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) def test_get_blob_exact_chunk_size(self): @@ -777,8 +777,8 @@ def test_get_blob_exact_chunk_size(self): # Arrange blob_name = self._get_blob_reference() byte_data = self.get_random_bytes( - self.config.blob_settings.max_single_get_size + - self.config.blob_settings.max_chunk_get_size) + self.config.max_single_get_size + + self.config.max_chunk_get_size) blob = self.bsc.get_blob_client(self.container_name, blob_name) blob.upload_blob(byte_data) @@ -796,8 +796,8 @@ def callback(response): self.assertEqual(byte_data, content) self.assert_download_progress( len(byte_data), - self.config.blob_settings.max_chunk_get_size, - self.config.blob_settings.max_single_get_size, + self.config.max_chunk_get_size, + self.config.max_single_get_size, progress) def test_get_blob_to_stream_with_md5(self): diff --git a/sdk/storage/azure-storage-blob/tests/test_large_block_blob.py b/sdk/storage/azure-storage-blob/tests/test_large_block_blob.py index 634eda3b6761..7bcfe8d15750 100644 --- a/sdk/storage/azure-storage-blob/tests/test_large_block_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_large_block_blob.py @@ -241,7 +241,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) def test_create_large_blob_from_path_with_properties(self): # parallel tests introduce random order of requests, can only run live @@ -312,8 +312,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob_name, data) - self.assert_upload_progress( - len(data), self.config.blob_settings.max_block_size, progress) + self.assert_upload_progress(len(data), self.config.max_block_size, progress) def test_create_large_blob_from_stream_chunked_upload_with_count(self): # parallel tests introduce random order of requests, can only run live diff --git a/sdk/storage/azure-storage-blob/tests/test_logging.py b/sdk/storage/azure-storage-blob/tests/test_logging.py index 3be41a564637..82c700d878fd 100644 --- a/sdk/storage/azure-storage-blob/tests/test_logging.py +++ b/sdk/storage/azure-storage-blob/tests/test_logging.py @@ -17,7 +17,7 @@ ContainerPermissions, BlobPermissions ) -from azure.storage.blob._shared.utils import _QueryStringConstants +from azure.storage.blob._shared.shared_access_signature import QueryStringConstants from testcase import ( StorageTestCase, @@ -96,7 +96,7 @@ def test_sas_signature_is_scrubbed_off(self): ) # parse out the signed signature token_components = parse_qs(token) - signed_signature = quote(token_components[_QueryStringConstants.SIGNED_SIGNATURE][0]) + signed_signature = quote(token_components[QueryStringConstants.SIGNED_SIGNATURE][0]) sas_service = ContainerClient(container.url, credential=token) @@ -107,7 +107,7 @@ def test_sas_signature_is_scrubbed_off(self): # Assert # make sure the query parameter 'sig' is logged, but its value is not - self.assertTrue(_QueryStringConstants.SIGNED_SIGNATURE in log_as_str) + self.assertTrue(QueryStringConstants.SIGNED_SIGNATURE in log_as_str) self.assertFalse(signed_signature in log_as_str) @record @@ -122,7 +122,7 @@ def test_copy_source_sas_is_scrubbed_off(self): # parse out the signed signature token_components = parse_qs(self.source_blob_url) - signed_signature = quote(token_components[_QueryStringConstants.SIGNED_SIGNATURE][0]) + signed_signature = quote(token_components[QueryStringConstants.SIGNED_SIGNATURE][0]) # Act with LogCaptured(self) as log_captured: @@ -132,7 +132,7 @@ def test_copy_source_sas_is_scrubbed_off(self): # Assert # make sure the query parameter 'sig' is logged, but its value is not - self.assertTrue(_QueryStringConstants.SIGNED_SIGNATURE in log_as_str) + self.assertTrue(QueryStringConstants.SIGNED_SIGNATURE in log_as_str) self.assertFalse(signed_signature in log_as_str) # make sure authorization header is logged, but its value is not diff --git a/sdk/storage/azure-storage-blob/tests/test_page_blob.py b/sdk/storage/azure-storage-blob/tests/test_page_blob.py index ca0231c9135b..e048a9c454cc 100644 --- a/sdk/storage/azure-storage-blob/tests/test_page_blob.py +++ b/sdk/storage/azure-storage-blob/tests/test_page_blob.py @@ -575,8 +575,7 @@ def callback(response): self.assertBlobEqual(self.container_name, blob.blob_name, data) self.assertEqual(props.etag, create_resp.get('etag')) self.assertEqual(props.last_modified, create_resp.get('last_modified')) - self.assert_upload_progress( - LARGE_BLOB_SIZE, self.config.blob_settings.max_page_size, progress) + self.assert_upload_progress(LARGE_BLOB_SIZE, self.config.max_page_size, progress) def test_create_blob_from_bytes_with_index(self): # parallel tests introduce random order of requests, can only run live @@ -657,7 +656,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob.blob_name, data) - self.assert_upload_progress(len(data), self.config.blob_settings.max_page_size, progress) + self.assert_upload_progress(len(data), self.config.max_page_size, progress) def test_create_blob_from_stream(self): # parallel tests introduce random order of requests, can only run live @@ -763,7 +762,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob.blob_name, data[:blob_size]) - self.assert_upload_progress(len(data), self.config.blob_settings.max_page_size, progress) + self.assert_upload_progress(len(data), self.config.max_page_size, progress) def test_create_blob_from_stream_truncated(self): # parallel tests introduce random order of requests, can only run live @@ -810,7 +809,7 @@ def callback(response): # Assert self.assertBlobEqual(self.container_name, blob.blob_name, data[:blob_size]) - self.assert_upload_progress(blob_size, self.config.blob_settings.max_page_size, progress) + self.assert_upload_progress(blob_size, self.config.max_page_size, progress) @record def test_create_blob_with_md5_small(self): diff --git a/sdk/storage/azure-storage-file/azure/storage/file/__init__.py b/sdk/storage/azure-storage-file/azure/storage/file/__init__.py index 73a47d5ed086..8ed3edced0cb 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/__init__.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/__init__.py @@ -20,6 +20,8 @@ SharePropertiesPaged, DirectoryProperties, DirectoryPropertiesPaged, + Handle, + HandlesPaged, FileProperties, Metrics, RetentionPolicy, @@ -56,5 +58,7 @@ 'DirectoryProperties', 'DirectoryPropertiesPaged', 'FileProperties', - 'ContentSettings' + 'ContentSettings', + 'Handle', + 'HandlesPaged' ] diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_deserialize.py b/sdk/storage/azure-storage-file/azure/storage/file/_deserialize.py new file mode 100644 index 000000000000..1ce2bd15e9cc --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_deserialize.py @@ -0,0 +1,46 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from .models import ShareProperties, DirectoryProperties, FileProperties +from ._shared.response_handlers import deserialize_metadata + + +def deserialize_share_properties(response, obj, headers): + metadata = deserialize_metadata(response, obj, headers) + share_properties = ShareProperties( + metadata=metadata, + **headers + ) + return share_properties + + +def deserialize_directory_properties(response, obj, headers): + metadata = deserialize_metadata(response, obj, headers) + directory_properties = DirectoryProperties( + metadata=metadata, + **headers + ) + return directory_properties + + +def deserialize_file_properties(response, obj, headers): + metadata = deserialize_metadata(response, obj, headers) + file_properties = FileProperties( + metadata=metadata, + **headers + ) + if 'Content-Range' in headers: + if 'x-ms-content-md5' in headers: + file_properties.content_settings.content_md5 = headers['x-ms-content-md5'] + else: + file_properties.content_settings.content_md5 = None + return file_properties + + +def deserialize_file_stream(response, obj, headers): + file_properties = deserialize_file_properties(response, obj, headers) + obj.properties = file_properties + return response.location_mode, obj diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_file_operations_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_file_operations_async.py index 1c8d7970a5bc..d7c358b58c5e 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_file_operations_async.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_file_operations_async.py @@ -190,7 +190,63 @@ async def download(self, timeout=None, range=None, range_get_content_md5=None, * raise models.StorageErrorException(response, self._deserialize) header_dict = {} - deserialized = response.stream_download() + deserialized = None + if response.status_code == 200: + deserialized = response.stream_download(self._client._pipeline) + header_dict = { + 'Last-Modified': self._deserialize('rfc-1123', response.headers.get('Last-Modified')), + 'x-ms-meta': self._deserialize('{str}', response.headers.get('x-ms-meta')), + 'Content-Length': self._deserialize('long', response.headers.get('Content-Length')), + 'Content-Type': self._deserialize('str', response.headers.get('Content-Type')), + 'Content-Range': self._deserialize('str', response.headers.get('Content-Range')), + 'ETag': self._deserialize('str', response.headers.get('ETag')), + 'Content-MD5': self._deserialize('bytearray', response.headers.get('Content-MD5')), + 'Content-Encoding': self._deserialize('str', response.headers.get('Content-Encoding')), + 'Cache-Control': self._deserialize('str', response.headers.get('Cache-Control')), + 'Content-Disposition': self._deserialize('str', response.headers.get('Content-Disposition')), + 'Content-Language': self._deserialize('str', response.headers.get('Content-Language')), + 'x-ms-copy-completion-time': self._deserialize('rfc-1123', response.headers.get('x-ms-copy-completion-time')), + 'x-ms-copy-status-description': self._deserialize('str', response.headers.get('x-ms-copy-status-description')), + 'x-ms-copy-id': self._deserialize('str', response.headers.get('x-ms-copy-id')), + 'x-ms-copy-progress': self._deserialize('str', response.headers.get('x-ms-copy-progress')), + 'x-ms-copy-source': self._deserialize('str', response.headers.get('x-ms-copy-source')), + 'x-ms-copy-status': self._deserialize(models.CopyStatusType, response.headers.get('x-ms-copy-status')), + 'x-ms-request-id': self._deserialize('str', response.headers.get('x-ms-request-id')), + 'x-ms-version': self._deserialize('str', response.headers.get('x-ms-version')), + 'Accept-Ranges': self._deserialize('str', response.headers.get('Accept-Ranges')), + 'Date': self._deserialize('rfc-1123', response.headers.get('Date')), + 'x-ms-server-encrypted': self._deserialize('bool', response.headers.get('x-ms-server-encrypted')), + 'x-ms-content-md5': self._deserialize('bytearray', response.headers.get('x-ms-content-md5')), + 'x-ms-error-code': self._deserialize('str', response.headers.get('x-ms-error-code')), + } + if response.status_code == 206: + deserialized = response.stream_download(self._client._pipeline) + header_dict = { + 'Last-Modified': self._deserialize('rfc-1123', response.headers.get('Last-Modified')), + 'x-ms-meta': self._deserialize('{str}', response.headers.get('x-ms-meta')), + 'Content-Length': self._deserialize('long', response.headers.get('Content-Length')), + 'Content-Type': self._deserialize('str', response.headers.get('Content-Type')), + 'Content-Range': self._deserialize('str', response.headers.get('Content-Range')), + 'ETag': self._deserialize('str', response.headers.get('ETag')), + 'Content-MD5': self._deserialize('bytearray', response.headers.get('Content-MD5')), + 'Content-Encoding': self._deserialize('str', response.headers.get('Content-Encoding')), + 'Cache-Control': self._deserialize('str', response.headers.get('Cache-Control')), + 'Content-Disposition': self._deserialize('str', response.headers.get('Content-Disposition')), + 'Content-Language': self._deserialize('str', response.headers.get('Content-Language')), + 'x-ms-copy-completion-time': self._deserialize('rfc-1123', response.headers.get('x-ms-copy-completion-time')), + 'x-ms-copy-status-description': self._deserialize('str', response.headers.get('x-ms-copy-status-description')), + 'x-ms-copy-id': self._deserialize('str', response.headers.get('x-ms-copy-id')), + 'x-ms-copy-progress': self._deserialize('str', response.headers.get('x-ms-copy-progress')), + 'x-ms-copy-source': self._deserialize('str', response.headers.get('x-ms-copy-source')), + 'x-ms-copy-status': self._deserialize(models.CopyStatusType, response.headers.get('x-ms-copy-status')), + 'x-ms-request-id': self._deserialize('str', response.headers.get('x-ms-request-id')), + 'x-ms-version': self._deserialize('str', response.headers.get('x-ms-version')), + 'Accept-Ranges': self._deserialize('str', response.headers.get('Accept-Ranges')), + 'Date': self._deserialize('rfc-1123', response.headers.get('Date')), + 'x-ms-server-encrypted': self._deserialize('bool', response.headers.get('x-ms-server-encrypted')), + 'x-ms-content-md5': self._deserialize('bytearray', response.headers.get('x-ms-content-md5')), + 'x-ms-error-code': self._deserialize('str', response.headers.get('x-ms-error-code')), + } if cls: return cls(response, deserialized, header_dict) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_share_operations_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_share_operations_async.py index 52f5eb2b5883..83e81cf64891 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_share_operations_async.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_generated/aio/operations_async/_share_operations_async.py @@ -518,6 +518,7 @@ async def set_access_policy(self, share_acl=None, timeout=None, *, cls=None, **k header_parameters['x-ms-version'] = self._serialize.header("self._config.version", self._config.version, 'str') # Construct body + serialization_ctxt = {'xml': {'name': 'SignedIdentifiers', 'itemsName': 'SignedIdentifier', 'wrapped': True}} if share_acl is not None: body_content = self._serialize.serialize_iter(share_acl, 'SignedIdentifier', serialization_ctxt=serialization_ctxt) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_polling.py b/sdk/storage/azure-storage-file/azure/storage/file/_polling.py new file mode 100644 index 000000000000..eade3e93394b --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_polling.py @@ -0,0 +1,66 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import logging +import time +from typing import Any, Callable +from azure.core.polling import PollingMethod + +from ._shared.response_handlers import process_storage_error +from ._generated.models import StorageErrorException + + +logger = logging.getLogger(__name__) + + +class CloseHandles(PollingMethod): + + def __init__(self, interval): + self._command = None + self._status = None + self._exception = None + self.handles_closed = 0 + self.polling_interval = interval + + def _update_status(self): + try: + status = self._command() # pylint: disable=protected-access + except StorageErrorException as error: + process_storage_error(error) + self._status = status.get('marker') + self.handles_closed += status['number_of_handles_closed'] + + def initialize(self, command, initial_status, _): # pylint: disable=arguments-differ + # type: (Any, Any, Callable) -> None + self._command = command + self._status = initial_status['marker'] + self.handles_closed = initial_status['number_of_handles_closed'] + + def run(self): + # type: () -> None + try: + while not self.finished(): + self._update_status() + time.sleep(self.polling_interval) + except Exception as e: + logger.warning(str(e)) + raise + + def status(self): + self._update_status() + return self.handles_closed + + def finished(self): + # type: () -> bool + """Is this polling finished? + :rtype: bool + """ + return self._status is None + + def resource(self): + # type: () -> Any + if not self.finished: + self._update_status() + return self.handles_closed diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_share_utils.py b/sdk/storage/azure-storage-file/azure/storage/file/_share_utils.py deleted file mode 100644 index 0d7de5697c77..000000000000 --- a/sdk/storage/azure-storage-file/azure/storage/file/_share_utils.py +++ /dev/null @@ -1,352 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - -import sys -from io import BytesIO - -from .models import ShareProperties, DirectoryProperties, FileProperties -from ._generated.models import StorageErrorException -from ._shared.utils import process_storage_error, parse_length_from_content_range -from ._shared.upload_chunking import upload_file_chunks -from ._shared.download_chunking import ( - validate_and_format_range_headers, - process_range_and_offset, - process_content, - ParallelFileChunkDownloader, - SequentialFileChunkDownloader) - - -def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argument - raw_metadata = {k: v for k, v in response.headers.items() if k.startswith("x-ms-meta-")} - return {k[10:]: v for k, v in raw_metadata.items()} - - -def deserialize_share_properties(response, obj, headers): - metadata = deserialize_metadata(response, obj, headers) - share_properties = ShareProperties( - metadata=metadata, - **headers - ) - return share_properties - - -def deserialize_directory_properties(response, obj, headers): - metadata = deserialize_metadata(response, obj, headers) - directory_properties = DirectoryProperties( - metadata=metadata, - **headers - ) - return directory_properties - - -def deserialize_file_properties(response, obj, headers): - metadata = deserialize_metadata(response, obj, headers) - file_properties = FileProperties( - metadata=metadata, - **headers - ) - if 'Content-Range' in headers: - if 'x-ms-content-md5' in headers: - file_properties.content_settings.content_md5 = headers['x-ms-content-md5'] - else: - file_properties.content_settings.content_md5 = None - return file_properties - - -def deserialize_file_stream(response, obj, headers): - file_properties = deserialize_file_properties(response, obj, headers) - obj.properties = file_properties - return response.location_mode, obj - - -def upload_file_helper( - client, - stream, - size, - metadata, - content_settings, - validate_content, - timeout, - max_connections, - file_settings, - **kwargs): - try: - if size is None or size < 0: - raise ValueError("A content size must be specified for a File.") - response = client.create_file( - size, - content_settings=content_settings, - metadata=metadata, - timeout=timeout, - **kwargs - ) - if size == 0: - return response - - return upload_file_chunks( - file_service=client, - file_size=size, - block_size=file_settings.max_range_size, - stream=stream, - max_connections=max_connections, - validate_content=validate_content, - timeout=timeout, - **kwargs) - except StorageErrorException as error: - process_storage_error(error) - - -class StorageStreamDownloader(object): # pylint: disable=too-many-instance-attributes - """A streaming object to download a file. - - The stream downloader can iterated, or download to open file or stream - over multiple threads. - """ - - def __init__( - self, share, file_name, file_path, service, config, offset, - length, validate_content, timeout, **kwargs): - self.service = service - - self.config = config - self.offset = offset - self.length = length - self.timeout = timeout - self.validate_content = validate_content - self.request_options = kwargs - self.location_mode = None - self._download_complete = False - - # The service only provides transactional MD5s for chunks under 4MB. - # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first - # chunk so a transactional MD5 can be retrieved. - self.first_get_size = self.config.max_single_get_size if not self.validate_content \ - else self.config.max_chunk_get_size - initial_request_start = self.offset if self.offset is not None else 0 - if self.length is not None and self.length - self.offset < self.first_get_size: - initial_request_end = self.length - else: - initial_request_end = initial_request_start + self.first_get_size - 1 - - self.initial_range, self.initial_offset = process_range_and_offset( - initial_request_start, - initial_request_end, - self.length, - None, None) - - self.download_size = None - self.file_size = None - self.file = self._initial_request() - self.properties = self.file.properties - self.properties.name = file_name - self.properties.share = share - self.properties.path = file_path - - # Set the content length to the download size instead of the size of - # the last range - self.properties.size = self.download_size - - # Overwrite the content range to the user requested range - self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.file_size) - - # Overwrite the content MD5 as it is the MD5 for the last range instead - # of the stored MD5 - # TODO: Set to the stored MD5 when the service returns this - self.properties.content_md5 = None - - def __len__(self): - return self.download_size - - def __iter__(self): - if self.download_size == 0: - content = b"" - else: - content = process_content( - self.file, - self.initial_offset[0], - self.initial_offset[1], - False, None, None) - - if content is not None: - yield content - if self._download_complete: - return - - end_file = self.file_size - if self.length is not None: - # Use the length unless it is over the end of the file - end_file = min(self.file_size, self.length + 1) - - downloader = SequentialFileChunkDownloader( - file_service=self.service, - download_size=self.download_size, - chunk_size=self.config.max_chunk_get_size, - progress=self.first_get_size, - start_range=self.initial_range[1] + 1, # start where the first download ended - end_range=end_file, - stream=None, - validate_content=self.validate_content, - timeout=self.timeout, - use_location=self.location_mode, - cls=deserialize_file_stream, - **self.request_options) - - for chunk in downloader.get_chunk_offsets(): - yield downloader.yield_chunk(chunk) - - def _initial_request(self): - range_header, range_validation = validate_and_format_range_headers( - self.initial_range[0], - self.initial_range[1], - start_range_required=False, - end_range_required=False, - check_content_md5=self.validate_content) - - try: - location_mode, file_stream = self.service.download( - timeout=self.timeout, - range=range_header, - range_get_content_md5=range_validation, - validate_content=self.validate_content, - cls=deserialize_file_stream, - data_stream_total=None, - download_stream_current=0, - **self.request_options) - - # Check the location we read from to ensure we use the same one - # for subsequent requests. - self.location_mode = location_mode - - # Parse the total file size and adjust the download size if ranges - # were specified - self.file_size = parse_length_from_content_range(file_stream.properties.content_range) - if self.length is not None: - # Use the length unless it is over the end of the file - self.download_size = min(self.file_size, self.length - self.offset + 1) - elif self.offset is not None: - self.download_size = self.file_size - self.offset - else: - self.download_size = self.file_size - - except StorageErrorException as error: - if self.offset is None and error.response.status_code == 416: - # Get range will fail on an empty file. If the user did not - # request a range, do a regular get request in order to get - # any properties. - try: - _, file_stream = self.service.download( - timeout=self.timeout, - validate_content=self.validate_content, - cls=deserialize_file_stream, - data_stream_total=0, - download_stream_current=0, - **self.request_options) - except StorageErrorException as error: - process_storage_error(error) - - # Set the download size to empty - self.download_size = 0 - self.file_size = 0 - else: - process_storage_error(error) - - # If the file is small, the download is complete at this point. - # If file size is large, download the rest of the file in chunks. - if file_stream.properties.size == self.download_size: - self._download_complete = True - - return file_stream - - def content_as_bytes(self, max_connections=1): - """Download the contents of this file. - - This operation is blocking until all data is downloaded. - - :param int max_connections: - The number of parallel connections with which to download. - :rtype: bytes - """ - stream = BytesIO() - self.download_to_stream(stream, max_connections=max_connections) - return stream.getvalue() - - def content_as_text(self, max_connections=1, encoding='UTF-8'): - """Download the contents of this file, and decode as text. - - This operation is blocking until all data is downloaded. - - :param int max_connections: - The number of parallel connections with which to download. - :rtype: str - """ - content = self.content_as_bytes(max_connections=max_connections) - return content.decode(encoding) - - def download_to_stream(self, stream, max_connections=1): - """Download the contents of this file to a stream. - - :param stream: - The stream to download to. This can be an open file-handle, - or any writable stream. The stream must be seekable if the download - uses more than one parallel connection. - :returns: The properties of the downloaded file. - :rtype: ~azure.storage.file.models.FileProperties - """ - # the stream must be seekable if parallel download is required - if max_connections > 1: - error_message = "Target stream handle must be seekable." - if sys.version_info >= (3,) and not stream.seekable(): - raise ValueError(error_message) - - try: - stream.seek(stream.tell()) - except (NotImplementedError, AttributeError): - raise ValueError(error_message) - - if self.download_size == 0: - content = b"" - else: - content = process_content( - self.file, - self.initial_offset[0], - self.initial_offset[1], - False, None, None) - # Write the content to the user stream - # Clear file content since output has been written to user stream - if content is not None: - stream.write(content) - if self._download_complete: - return self.properties - - end_file = self.file_size - if self.length is not None: - # Use the length unless it is over the end of the file - end_file = min(self.file_size, self.length + 1) - - downloader_class = ParallelFileChunkDownloader if max_connections > 1 else SequentialFileChunkDownloader - downloader = downloader_class( - file_service=self.service, - download_size=self.download_size, - chunk_size=self.config.max_chunk_get_size, - progress=self.first_get_size, - start_range=self.initial_range[1] + 1, # start where the first download ended - end_range=end_file, - stream=stream, - validate_content=self.validate_content, - timeout=self.timeout, - use_location=self.location_mode, - cls=deserialize_file_stream, - **self.request_options) - - if max_connections > 1: - import concurrent.futures - executor = concurrent.futures.ThreadPoolExecutor(max_connections) - list(executor.map(downloader.process_chunk, downloader.get_chunk_offsets())) - else: - for chunk in downloader.get_chunk_offsets(): - downloader.process_chunk(chunk) - - return self.properties diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/__init__.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/__init__.py index 5b396cd202e8..160f88223820 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/__init__.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/__init__.py @@ -3,3 +3,54 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- + +import base64 +import hashlib +import hmac + +try: + from urllib.parse import quote, unquote +except ImportError: + from urllib2 import quote, unquote # type: ignore + +import six + + +def url_quote(url): + return quote(url) + + +def url_unquote(url): + return unquote(url) + + +def encode_base64(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + encoded = base64.b64encode(data) + return encoded.decode('utf-8') + + +def decode_base64_to_bytes(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + return base64.b64decode(data) + + +def decode_base64_to_text(data): + decoded_bytes = decode_base64_to_bytes(data) + return decoded_bytes.decode('utf-8') + + +def sign_string(key, string_to_sign, key_is_base64=True): + if key_is_base64: + key = decode_base64_to_bytes(key) + else: + if isinstance(key, six.text_type): + key = key.encode('utf-8') + if isinstance(string_to_sign, six.text_type): + string_to_sign = string_to_sign.encode('utf-8') + signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) + digest = signed_hmac_sha256.digest() + encoded_digest = encode_base64(digest) + return encoded_digest diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/authentication.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/authentication.py index 4a2c4532d924..43b1529ce988 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/authentication.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/authentication.py @@ -4,9 +4,6 @@ # license information. # -------------------------------------------------------------------------- -import base64 -import hashlib -import hmac import logging import sys try: @@ -18,43 +15,12 @@ from azure.core.exceptions import ClientAuthenticationError from azure.core.pipeline.policies import SansIOHTTPPolicy -if sys.version_info < (3,): - _unicode_type = unicode # pylint: disable=undefined-variable -else: - _unicode_type = str -logger = logging.getLogger(__name__) - - -def _encode_base64(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') - +from . import sign_string -def _decode_base64_to_bytes(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - return base64.b64decode(data) - -def _decode_base64_to_text(data): - decoded_bytes = _decode_base64_to_bytes(data) - return decoded_bytes.decode('utf-8') +logger = logging.getLogger(__name__) -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, _unicode_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, _unicode_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = _encode_base64(digest) - return encoded_digest # wraps a given exception with the desired exception type def _wrap_exception(ex, desired_type): @@ -125,7 +91,7 @@ def _get_canonicalized_resource_query(self, request): def _add_authorization_header(self, request, string_to_sign): try: - signature = _sign_string(self.account_key, string_to_sign) + signature = sign_string(self.account_key, string_to_sign) auth_string = 'SharedKey ' + self.account_name + ':' + signature request.http_request.headers['Authorization'] = auth_string except Exception as ex: @@ -134,6 +100,9 @@ def _add_authorization_header(self, request, string_to_sign): raise _wrap_exception(ex, AzureSigningError) def on_request(self, request, **kwargs): + if not 'content-type' in request.http_request.headers: + request.http_request.headers['content-type'] = 'application/xml; charset=utf-8' + string_to_sign = \ self._get_verb(request) + \ self._get_headers( diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client.py new file mode 100644 index 000000000000..1b526d505da2 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client.py @@ -0,0 +1,296 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging +try: + from urllib.parse import parse_qs +except ImportError: + from urlparse import parse_qs # type: ignore + +import six + +from azure.core import Configuration +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import RequestsTransport +from azure.core.pipeline.policies import ( + RedirectPolicy, + ContentDecodePolicy, + BearerTokenCredentialPolicy, + ProxyPolicy) + +from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT +from .models import LocationMode +from .authentication import SharedKeyCredentialPolicy +from .shared_access_signature import QueryStringConstants +from .policies import ( + StorageHeadersPolicy, + StorageUserAgentPolicy, + StorageContentValidation, + StorageRequestHook, + StorageResponseHook, + StorageLoggingPolicy, + StorageHosts, + QueueMessagePolicy, + ExponentialRetry) + + +_LOGGER = logging.getLogger(__name__) +_SERVICE_PARAMS = { + 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, + 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, + 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, +} + + +class StorageAccountHostsMixin(object): + + def __init__( + self, parsed_url, # type: Any + service, # type: str + credential=None, # type: Optional[Any] + **kwargs # type: Any + ): + # type: (...) -> None + self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) + self._hosts = kwargs.get('_hosts') + self.scheme = parsed_url.scheme + + if service not in ['blob', 'queue', 'file']: + raise ValueError("Invalid service: {}".format(service)) + account = parsed_url.netloc.split(".{}.core.".format(service)) + secondary_hostname = None + self.credential = format_shared_key_credential(account, credential) + if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): + raise ValueError("Token credential is only supported with HTTPS.") + if hasattr(self.credential, 'account_name'): + secondary_hostname = "{}-secondary.{}.{}".format( + self.credential.account_name, service, SERVICE_HOST_BASE) + + if not self._hosts: + if len(account) > 1: + secondary_hostname = parsed_url.netloc.replace( + account[0], + account[0] + '-secondary') + if kwargs.get('secondary_hostname'): + secondary_hostname = kwargs['secondary_hostname'] + self._hosts = { + LocationMode.PRIMARY: parsed_url.netloc, + LocationMode.SECONDARY: secondary_hostname} + + self.require_encryption = kwargs.get('require_encryption', False) + self.key_encryption_key = kwargs.get('key_encryption_key') + self.key_resolver_function = kwargs.get('key_resolver_function') + self._config, self._pipeline = self._create_pipeline(self.credential, storage_sdk=service, **kwargs) + + def __enter__(self): + self._client.__enter__() + return self + + def __exit__(self, *args): + self._client.__exit__(*args) + + @property + def url(self): + return self._format_url(self._hosts[self._location_mode]) + + @property + def primary_endpoint(self): + return self._format_url(self._hosts[LocationMode.PRIMARY]) + + @property + def primary_hostname(self): + return self._hosts[LocationMode.PRIMARY] + + @property + def secondary_endpoint(self): + if not self._hosts[LocationMode.SECONDARY]: + raise ValueError("No secondary host configured.") + return self._format_url(self._hosts[LocationMode.SECONDARY]) + + @property + def secondary_hostname(self): + return self._hosts[LocationMode.SECONDARY] + + @property + def location_mode(self): + return self._location_mode + + @location_mode.setter + def location_mode(self, value): + if self._hosts.get(value): + self._location_mode = value + self._client._config.url = self.url # pylint: disable=protected-access + else: + raise ValueError("No host URL for location mode: {}".format(value)) + + def _format_query_string(self, sas_token, credential, snapshot=None, share_snapshot=None): + query_str = "?" + if snapshot: + query_str += 'snapshot={}&'.format(self.snapshot) + if share_snapshot: + query_str += 'sharesnapshot={}&'.format(self.snapshot) + if sas_token and not credential: + query_str += sas_token + elif is_credential_sastoken(credential): + query_str += credential.lstrip('?') + credential = None + return query_str.rstrip('?&'), credential + + def _create_pipeline(self, credential, **kwargs): + # type: (Any, **Any) -> Tuple[Configuration, Pipeline] + credential_policy = None + if hasattr(credential, 'get_token'): + credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) + elif isinstance(credential, SharedKeyCredentialPolicy): + credential_policy = credential + elif credential is not None: + raise TypeError("Unsupported credential: {}".format(credential)) + + config = kwargs.get('_configuration') or create_configuration(**kwargs) + if kwargs.get('_pipeline'): + return config, kwargs['_pipeline'] + config.transport = kwargs.get('transport') # type: HttpTransport + if not config.transport: + config.transport = RequestsTransport(config) + policies = [ + QueueMessagePolicy(), + config.headers_policy, + config.user_agent_policy, + StorageContentValidation(), + StorageRequestHook(**kwargs), + credential_policy, + ContentDecodePolicy(), + RedirectPolicy(**kwargs), + StorageHosts(hosts=self._hosts, **kwargs), + config.retry_policy, + config.logging_policy, + StorageResponseHook(**kwargs), + ] + return config, Pipeline(config.transport, policies=policies) + + +def format_shared_key_credential(account, credential): + if isinstance(credential, six.string_types): + if len(account) < 2: + raise ValueError("Unable to determine account name for shared key credential.") + credential = { + 'account_name': account[0], + 'account_key': credential + } + if isinstance(credential, dict): + if 'account_name' not in credential: + raise ValueError("Shared key credential missing 'account_name") + if 'account_key' not in credential: + raise ValueError("Shared key credential missing 'account_key") + return SharedKeyCredentialPolicy(**credential) + return credential + + +def parse_connection_str(conn_str, credential, service): + conn_str = conn_str.rstrip(';') + conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension + endpoints = _SERVICE_PARAMS[service] + primary = None + secondary = None + if not credential: + try: + credential = { + 'account_name': conn_settings['AccountName'], + 'account_key': conn_settings['AccountKey'] + } + except KeyError: + credential = conn_settings.get('SharedAccessSignature') + if endpoints['primary'] in conn_settings: + primary = conn_settings[endpoints['primary']] + if endpoints['secondary'] in conn_settings: + secondary = conn_settings[endpoints['secondary']] + else: + if endpoints['secondary'] in conn_settings: + raise ValueError("Connection string specifies only secondary endpoint.") + try: + primary = "{}://{}.{}.{}".format( + conn_settings['DefaultEndpointsProtocol'], + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + secondary = "{}-secondary.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + except KeyError: + pass + + if not primary: + try: + primary = "https://{}.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) + ) + except KeyError: + raise ValueError("Connection string missing required connection details.") + return primary, secondary, credential + + +def create_configuration(**kwargs): + # type: (**Any) -> Configuration + if 'connection_timeout' not in kwargs: + kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT + config = Configuration(**kwargs) + config.headers_policy = StorageHeadersPolicy(**kwargs) + config.user_agent_policy = StorageUserAgentPolicy(**kwargs) + config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + config.logging_policy = StorageLoggingPolicy(**kwargs) + config.proxy_policy = ProxyPolicy(**kwargs) + + # Storage settings + config.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) + config.copy_polling_interval = 15 + + # Block blob uploads + config.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) + config.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) + config.use_byte_buffer = kwargs.get('use_byte_buffer', False) + + # Page blob uploads + config.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) + + # Blob downloads + config.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) + config.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) + + # File uploads + config.max_range_size = kwargs.get('max_range_size', 4 * 1024 * 1024) + return config + + +def parse_query(query_str): + sas_values = QueryStringConstants.to_list() + parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} + sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] + sas_token = None + if sas_params: + sas_token = '&'.join(sas_params) + + snapshot = parsed_query.get('snapshot') or parsed_query.get('sharesnapshot') + return snapshot, sas_token + + +def is_credential_sastoken(credential): + if not credential or not isinstance(credential, six.string_types): + return False + + sas_values = QueryStringConstants.to_list() + parsed_query = parse_qs(credential.lstrip('?')) + if parsed_query and all([k in sas_values for k in parsed_query.keys()]): + return True + return False diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client_async.py new file mode 100644 index 000000000000..2e15dd9e8813 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/base_client_async.py @@ -0,0 +1,84 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging + +from azure.core.pipeline import AsyncPipeline +try: + from azure.core.pipeline.transport import AioHttpTransport as AsyncTransport +except ImportError: + from azure.core.pipeline.transport import AsyncioRequestsTransport as AsyncTransport +from azure.core.pipeline.policies import ( + ContentDecodePolicy, + BearerTokenCredentialPolicy, + AsyncRedirectPolicy) + +from .constants import STORAGE_OAUTH_SCOPE, DEFAULT_SOCKET_TIMEOUT +from .authentication import SharedKeyCredentialPolicy +from .base_client import create_configuration +from .policies import ( + StorageContentValidation, + StorageRequestHook, + StorageHosts, + QueueMessagePolicy) +from .policies_async import AsyncStorageResponseHook + + +_LOGGER = logging.getLogger(__name__) + + +class AsyncStorageAccountHostsMixin(object): + + def __enter__(self): + raise TypeError("Async client only supports 'async with'.") + + def __exit__(self, *args): + pass + + async def __aenter__(self): + await self._client.__aenter__() + return self + + async def __aexit__(self, *args): + await self._client.__aexit__(*args) + + def _create_pipeline(self, credential, **kwargs): + # type: (Any, **Any) -> Tuple[Configuration, Pipeline] + credential_policy = None + if hasattr(credential, 'get_token'): + credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) + elif isinstance(credential, SharedKeyCredentialPolicy): + credential_policy = credential + elif credential is not None: + raise TypeError("Unsupported credential: {}".format(credential)) + + if 'connection_timeout' not in kwargs: + kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT[0] + config = kwargs.get('_configuration') or create_configuration(**kwargs) + if kwargs.get('_pipeline'): + return config, kwargs['_pipeline'] + config.transport = kwargs.get('transport') # type: HttpTransport + if not config.transport: + config.transport = AsyncTransport(config) + policies = [ + QueueMessagePolicy(), + config.headers_policy, + config.user_agent_policy, + StorageContentValidation(), + StorageRequestHook(**kwargs), + credential_policy, + ContentDecodePolicy(), + AsyncRedirectPolicy(**kwargs), + StorageHosts(hosts=self._hosts, **kwargs), + config.retry_policy, + config.logging_policy, + AsyncStorageResponseHook(**kwargs), + ] + return config, AsyncPipeline(config.transport, policies=policies) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/download_chunking.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/download_chunking.py deleted file mode 100644 index 6cf4d91e0fb8..000000000000 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/download_chunking.py +++ /dev/null @@ -1,324 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import threading - -from azure.core.exceptions import HttpResponseError - -from .models import ModifiedAccessConditions -from .utils import validate_and_format_range_headers, process_storage_error -from .encryption import _decrypt_blob - - -def process_range_and_offset(start_range, end_range, length, key_encryption_key, key_resolver_function): - start_offset, end_offset = 0, 0 - if key_encryption_key is not None or key_resolver_function is not None: - if start_range is not None: - # Align the start of the range along a 16 byte block - start_offset = start_range % 16 - start_range -= start_offset - - # Include an extra 16 bytes for the IV if necessary - # Because of the previous offsetting, start_range will always - # be a multiple of 16. - if start_range > 0: - start_offset += 16 - start_range -= 16 - - if length is not None: - # Align the end of the range along a 16 byte block - end_offset = 15 - (end_range % 16) - end_range += end_offset - - return (start_range, end_range), (start_offset, end_offset) - - -def process_content(blob, start_offset, end_offset, require_encryption, key_encryption_key, key_resolver_function): - if key_encryption_key is not None or key_resolver_function is not None: - try: - return _decrypt_blob( - require_encryption, - key_encryption_key, - key_resolver_function, - blob, - start_offset, - end_offset) - except Exception as error: - raise HttpResponseError( - message="Decryption failed.", - response=blob.response, - error=error) - else: - return b"".join(list(blob)) - - -class _BlobChunkDownloader(object): # pylint: disable=too-many-instance-attributes - - def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, stream, - validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): - # identifiers for the blob - self.blob_service = blob_service - - # information on the download range/chunk size - self.chunk_size = chunk_size - self.download_size = download_size - self.start_index = start_range - self.blob_end = end_range - - # the destination that we will write to - self.stream = stream - - # download progress so far - self.progress_total = progress - - # encryption - self.require_encryption = require_encryption - self.key_encryption_key = key_encryption_key - self.key_resolver_function = key_resolver_function - - # parameters for each get blob operation - self.timeout = timeout - self.validate_content = validate_content - self.access_conditions = access_conditions - self.mod_conditions = mod_conditions - self.request_options = kwargs - - def _calculate_range(self, chunk_start): - if chunk_start + self.chunk_size > self.blob_end: - chunk_end = self.blob_end - else: - chunk_end = chunk_start + self.chunk_size - return chunk_start, chunk_end - - def get_chunk_offsets(self): - index = self.start_index - while index < self.blob_end: - yield index - index += self.chunk_size - - def process_chunk(self, chunk_start): - chunk_start, chunk_end = self._calculate_range(chunk_start) - chunk_data = self._download_chunk(chunk_start, chunk_end) - length = chunk_end - chunk_start - if length > 0: - self._write_to_stream(chunk_data, chunk_start) - self._update_progress(length) - - def yield_chunk(self, chunk_start): - chunk_start, chunk_end = self._calculate_range(chunk_start) - return self._download_chunk(chunk_start, chunk_end) - - # should be provided by the subclass - def _update_progress(self, length): - pass - - # should be provided by the subclass - def _write_to_stream(self, chunk_data, chunk_start): - pass - - def _download_chunk(self, chunk_start, chunk_end): - download_range, offset = process_range_and_offset( - chunk_start, - chunk_end, - chunk_end, - self.key_encryption_key, - self.key_resolver_function, - ) - range_header, range_validation = validate_and_format_range_headers( - download_range[0], - download_range[1] - 1, - check_content_md5=self.validate_content) - - try: - _, response = self.blob_service.download( - timeout=self.timeout, - range=range_header, - range_get_content_md5=range_validation, - lease_access_conditions=self.access_conditions, - modified_access_conditions=self.mod_conditions, - validate_content=self.validate_content, - data_stream_total=self.download_size, - download_stream_current=self.progress_total, - **self.request_options) - except HttpResponseError as error: - process_storage_error(error) - - chunk_data = process_content( - response, - offset[0], - offset[1], - self.require_encryption, - self.key_encryption_key, - self.key_resolver_function) - - # This makes sure that if_match is set so that we can validate - # that subsequent downloads are to an unmodified blob - if not self.mod_conditions: - self.mod_conditions = ModifiedAccessConditions() - self.mod_conditions.if_match = response.properties.etag - return chunk_data - - -class ParallelBlobChunkDownloader(_BlobChunkDownloader): - def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): - - super(ParallelBlobChunkDownloader, self).__init__( - blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs) - - # for a parallel download, the stream is always seekable, so we note down the current position - # in order to seek to the right place when out-of-order chunks come in - self.stream_start = stream.tell() - - # since parallel operations are going on - # it is essential to protect the writing and progress reporting operations - self.stream_lock = threading.Lock() - self.progress_lock = threading.Lock() - - def _update_progress(self, length): - with self.progress_lock: - self.progress_total += length - - def _write_to_stream(self, chunk_data, chunk_start): - with self.stream_lock: - self.stream.seek(self.stream_start + (chunk_start - self.start_index)) - self.stream.write(chunk_data) - - -class SequentialBlobChunkDownloader(_BlobChunkDownloader): - - def _update_progress(self, length): - self.progress_total += length - - def _write_to_stream(self, chunk_data, chunk_start): - # chunk_start is ignored in the case of sequential download since we cannot seek the destination stream - self.stream.write(chunk_data) - - -class _FileChunkDownloader(object): - def __init__(self, file_service, download_size, chunk_size, progress, - start_range, end_range, stream, validate_content, timeout, **kwargs): - # identifiers for the file - self.file_service = file_service - - # information on the download range/chunk size - self.chunk_size = chunk_size - self.download_size = download_size - self.start_index = start_range - self.file_end = end_range - - # the destination that we will write to - self.stream = stream - - # progress related - self.progress_total = progress - - # parameters for each get file operation - self.validate_content = validate_content - self.timeout = timeout - self.request_options = kwargs - - def _calculate_range(self, chunk_start): - if chunk_start + self.chunk_size > self.file_end: - chunk_end = self.file_end - else: - chunk_end = chunk_start + self.chunk_size - return chunk_start, chunk_end - - def get_chunk_offsets(self): - index = self.start_index - while index < self.file_end: - yield index - index += self.chunk_size - - def process_chunk(self, chunk_start): - chunk_start, chunk_end = self._calculate_range(chunk_start) - chunk_data = self._download_chunk(chunk_start, chunk_end) - length = chunk_end - chunk_start - if length > 0: - self._write_to_stream(chunk_data, chunk_start) - self._update_progress(length) - - def yield_chunk(self, chunk_start): - chunk_start, chunk_end = self._calculate_range(chunk_start) - return self._download_chunk(chunk_start, chunk_end) - - # should be provided by the subclass - def _update_progress(self, length): - pass - - # should be provided by the subclass - def _write_to_stream(self, chunk_data, chunk_start): - pass - - def _download_chunk(self, chunk_start, chunk_end): - download_range, offset = process_range_and_offset( - chunk_start, - chunk_end, - chunk_end, - None, - None, - ) - range_header, range_validation = validate_and_format_range_headers( - download_range[0], - download_range[1] - 1, - check_content_md5=self.validate_content) - try: - _, response = self.file_service.download( - timeout=self.timeout, - range=range_header, - range_get_content_md5=range_validation, - validate_content=self.validate_content, - data_stream_total=self.download_size, - download_stream_current=self.progress_total, - **self.request_options) - except HttpResponseError as error: - process_storage_error(error) - - chunk_data = process_content(response, offset[0], offset[1], False, None, None) - return chunk_data - - -class ParallelFileChunkDownloader(_FileChunkDownloader): - def __init__( - self, file_service, download_size, chunk_size, progress, - start_range, end_range, stream, validate_content, timeout, **kwargs): - super(ParallelFileChunkDownloader, self).__init__( - file_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, timeout, **kwargs) - - # for a parallel download, the stream is always seekable, so we note down the current position - # in order to seek to the right place when out-of-order chunks come in - self.stream_start = stream.tell() - - # since parallel operations are going on - # it is essential to protect the writing and progress reporting operations - self.stream_lock = threading.Lock() - self.progress_lock = threading.Lock() - - def _update_progress(self, length): - with self.progress_lock: - self.progress_total += length - - def _write_to_stream(self, chunk_data, chunk_start): - with self.stream_lock: - self.stream.seek(self.stream_start + (chunk_start - self.start_index)) - self.stream.write(chunk_data) - - -class SequentialFileChunkDownloader(_FileChunkDownloader): - - def _update_progress(self, length): - self.progress_total += length - - def _write_to_stream(self, chunk_data, chunk_start): - # chunk_start is ignored in the case of sequential download since we cannot seek the destination stream - self.stream.write(chunk_data) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads.py new file mode 100644 index 000000000000..1d46ffc95293 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads.py @@ -0,0 +1,462 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import sys +import threading +from io import BytesIO + +from azure.core.exceptions import HttpResponseError + +from .request_handlers import validate_and_format_range_headers +from .response_handlers import process_storage_error, parse_length_from_content_range +from .encryption import decrypt_blob + + +def process_range_and_offset(start_range, end_range, length, encryption): + start_offset, end_offset = 0, 0 + if encryption.get('key') is not None or encryption.get('resolver') is not None: + if start_range is not None: + # Align the start of the range along a 16 byte block + start_offset = start_range % 16 + start_range -= start_offset + + # Include an extra 16 bytes for the IV if necessary + # Because of the previous offsetting, start_range will always + # be a multiple of 16. + if start_range > 0: + start_offset += 16 + start_range -= 16 + + if length is not None: + # Align the end of the range along a 16 byte block + end_offset = 15 - (end_range % 16) + end_range += end_offset + + return (start_range, end_range), (start_offset, end_offset) + + +def process_content(data, start_offset, end_offset, encryption): + if data is None: + raise ValueError("Response cannot be None.") + content = b"".join(list(data)) + if content and encryption.get('key') is not None or encryption.get('resolver') is not None: + try: + return decrypt_blob( + encryption.get('required'), + encryption.get('key'), + encryption.get('resolver'), + content, + start_offset, + end_offset, + data.response.headers) + except Exception as error: + raise HttpResponseError( + message="Decryption failed.", + response=data.response, + error=error) + return content + + +class _ChunkDownloader(object): + + def __init__( + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + + self.service = service + + # information on the download range/chunk size + self.chunk_size = chunk_size + self.total_size = total_size + self.start_index = start_range + self.end_index = end_range + + # the destination that we will write to + self.stream = stream + + # download progress so far + self.progress_total = current_progress + + # encryption + self.encryption_options = encryption_options + + # parameters for each get operation + self.validate_content = validate_content + self.request_options = kwargs + + def _calculate_range(self, chunk_start): + if chunk_start + self.chunk_size > self.end_index: + chunk_end = self.end_index + else: + chunk_end = chunk_start + self.chunk_size + return chunk_start, chunk_end + + def get_chunk_offsets(self): + index = self.start_index + while index < self.end_index: + yield index + index += self.chunk_size + + def process_chunk(self, chunk_start): + chunk_start, chunk_end = self._calculate_range(chunk_start) + chunk_data = self._download_chunk(chunk_start, chunk_end) + length = chunk_end - chunk_start + if length > 0: + self._write_to_stream(chunk_data, chunk_start) + self._update_progress(length) + + def yield_chunk(self, chunk_start): + chunk_start, chunk_end = self._calculate_range(chunk_start) + return self._download_chunk(chunk_start, chunk_end) + + # should be provided by the subclass + def _update_progress(self, length): + pass + + # should be provided by the subclass + def _write_to_stream(self, chunk_data, chunk_start): + pass + + def _download_chunk(self, chunk_start, chunk_end): + download_range, offset = process_range_and_offset( + chunk_start, chunk_end, chunk_end, self.encryption_options) + range_header, range_validation = validate_and_format_range_headers( + download_range[0], + download_range[1] - 1, + check_content_md5=self.validate_content) + + try: + _, response = self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=self.total_size, + download_stream_current=self.progress_total, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + chunk_data = process_content(response, offset[0], offset[1], self.encryption_options) + + # This makes sure that if_match is set so that we can validate + # that subsequent downloads are to an unmodified blob + if self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = response.properties.etag + + return chunk_data + + +class ParallelChunkDownloader(_ChunkDownloader): + + def __init__( + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + super(ParallelChunkDownloader, self).__init__( + service=service, + total_size=total_size, + chunk_size=chunk_size, + current_progress=current_progress, + start_range=start_range, + end_range=end_range, + stream=stream, + validate_content=validate_content, + encryption_options=encryption_options, + **kwargs) + + # for a parallel download, the stream is always seekable, so we note down the current position + # in order to seek to the right place when out-of-order chunks come in + self.stream_start = stream.tell() + + # since parallel operations are going on + # it is essential to protect the writing and progress reporting operations + self.stream_lock = threading.Lock() + self.progress_lock = threading.Lock() + + def _update_progress(self, length): + with self.progress_lock: + self.progress_total += length + + def _write_to_stream(self, chunk_data, chunk_start): + with self.stream_lock: + self.stream.seek(self.stream_start + (chunk_start - self.start_index)) + self.stream.write(chunk_data) + + +class SequentialChunkDownloader(_ChunkDownloader): + + def _update_progress(self, length): + self.progress_total += length + + def _write_to_stream(self, chunk_data, chunk_start): + # chunk_start is ignored in the case of sequential download since we cannot seek the destination stream + self.stream.write(chunk_data) + + +class StorageStreamDownloader(object): # pylint: disable=too-many-instance-attributes + """A streaming object to download from Azure Storage. + + The stream downloader can iterated, or download to open file or stream + over multiple threads. + """ + + def __init__( + self, service=None, + config=None, + offset=None, + length=None, + validate_content=None, + encryption_options=None, + extra_properties=None, + **kwargs): + self.service = service + self.config = config + self.offset = offset + self.length = length + self.validate_content = validate_content + self.encryption_options = encryption_options or {} + self.request_options = kwargs + self.location_mode = None + self._download_complete = False + + # The service only provides transactional MD5s for chunks under 4MB. + # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first + # chunk so a transactional MD5 can be retrieved. + self.first_get_size = self.config.max_single_get_size if not self.validate_content \ + else self.config.max_chunk_get_size + initial_request_start = self.offset if self.offset is not None else 0 + if self.length is not None and self.length - self.offset < self.first_get_size: + initial_request_end = self.length + else: + initial_request_end = initial_request_start + self.first_get_size - 1 + + self.initial_range, self.initial_offset = process_range_and_offset( + initial_request_start, initial_request_end, self.length, self.encryption_options) + + self.download_size = None + self.file_size = None + self.response = self._initial_request() + self.properties = self.response.properties + + # Set the content length to the download size instead of the size of + # the last range + self.properties.size = self.download_size + + # Overwrite the content range to the user requested range + self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.file_size) + + # Set additional properties according to download type + if extra_properties: + for prop, value in extra_properties.items(): + setattr(self.properties, prop, value) + + # Overwrite the content MD5 as it is the MD5 for the last range instead + # of the stored MD5 + # TODO: Set to the stored MD5 when the service returns this + self.properties.content_md5 = None + + def __len__(self): + return self.download_size + + def __iter__(self): + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + if content is not None: + yield content + if self._download_complete: + return + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader = SequentialChunkDownloader( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=None, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + for chunk in downloader.get_chunk_offsets(): + yield downloader.yield_chunk(chunk) + + def _initial_request(self): + range_header, range_validation = validate_and_format_range_headers( + self.initial_range[0], + self.initial_range[1], + start_range_required=False, + end_range_required=False, + check_content_md5=self.validate_content) + + try: + location_mode, response = self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=None, + download_stream_current=0, + **self.request_options) + + # Check the location we read from to ensure we use the same one + # for subsequent requests. + self.location_mode = location_mode + + # Parse the total file size and adjust the download size if ranges + # were specified + self.file_size = parse_length_from_content_range(response.properties.content_range) + if self.length is not None: + # Use the length unless it is over the end of the file + self.download_size = min(self.file_size, self.length - self.offset + 1) + elif self.offset is not None: + self.download_size = self.file_size - self.offset + else: + self.download_size = self.file_size + + except HttpResponseError as error: + if self.offset is None and error.response.status_code == 416: + # Get range will fail on an empty file. If the user did not + # request a range, do a regular get request in order to get + # any properties. + try: + _, response = self.service.download( + validate_content=self.validate_content, + data_stream_total=0, + download_stream_current=0, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + # Set the download size to empty + self.download_size = 0 + self.file_size = 0 + else: + process_storage_error(error) + + # If the file is small, the download is complete at this point. + # If file size is large, download the rest of the file in chunks. + if response.properties.size != self.download_size: + # Lock on the etag. This can be overriden by the user by specifying '*' + if self.request_options.get('modified_access_conditions'): + if not self.request_options['modified_access_conditions'].if_match: + self.request_options['modified_access_conditions'].if_match = response.properties.etag + else: + self._download_complete = True + + return response + + + def content_as_bytes(self, max_connections=1): + """Download the contents of this file. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: bytes + """ + stream = BytesIO() + self.download_to_stream(stream, max_connections=max_connections) + return stream.getvalue() + + def content_as_text(self, max_connections=1, encoding='UTF-8'): + """Download the contents of this file, and decode as text. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: str + """ + content = self.content_as_bytes(max_connections=max_connections) + return content.decode(encoding) + + def download_to_stream(self, stream, max_connections=1): + """Download the contents of this file to a stream. + + :param stream: + The stream to download to. This can be an open file-handle, + or any writable stream. The stream must be seekable if the download + uses more than one parallel connection. + :returns: The properties of the downloaded file. + :rtype: Any + """ + # the stream must be seekable if parallel download is required + if max_connections > 1: + error_message = "Target stream handle must be seekable." + if sys.version_info >= (3,) and not stream.seekable(): + raise ValueError(error_message) + + try: + stream.seek(stream.tell()) + except (NotImplementedError, AttributeError): + raise ValueError(error_message) + + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + # Write the content to the user stream + if content is not None: + stream.write(content) + if self._download_complete: + return self.properties + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader_class = ParallelChunkDownloader if max_connections > 1 else SequentialChunkDownloader + downloader = downloader_class( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + if max_connections > 1: + import concurrent.futures + executor = concurrent.futures.ThreadPoolExecutor(max_connections) + list(executor.map(downloader.process_chunk, downloader.get_chunk_offsets())) + else: + for chunk in downloader.get_chunk_offsets(): + downloader.process_chunk(chunk) + + return self.properties diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads_async.py new file mode 100644 index 000000000000..f3d1bf1be885 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/downloads_async.py @@ -0,0 +1,431 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import sys +import asyncio +from io import BytesIO +from itertools import islice + +from azure.core.exceptions import HttpResponseError + +from .request_handlers import validate_and_format_range_headers +from .response_handlers import process_storage_error, parse_length_from_content_range +from .encryption import decrypt_blob +from .downloads import process_range_and_offset + + +async def process_content(data, start_offset, end_offset, encryption): + if data is None: + raise ValueError("Response cannot be None.") + content = data.response.body + if encryption.get('key') is not None or encryption.get('resolver') is not None: + try: + return decrypt_blob( + encryption.get('required'), + encryption.get('key'), + encryption.get('resolver'), + content, + start_offset, + end_offset, + data.response.headers) + except Exception as error: + raise HttpResponseError( + message="Decryption failed.", + response=data.response, + error=error) + return content + + +class _AsyncChunkDownloader(object): # pylint: disable=too-many-instance-attributes + + def __init__( + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + parallel=None, + validate_content=None, + encryption_options=None, + **kwargs): + + self.service = service + + # information on the download range/chunk size + self.chunk_size = chunk_size + self.total_size = total_size + self.start_index = start_range + self.end_index = end_range + + # the destination that we will write to + self.stream = stream + self.stream_lock = asyncio.Lock() if parallel else None + self.progress_lock = asyncio.Lock() if parallel else None + + # for a parallel download, the stream is always seekable, so we note down the current position + # in order to seek to the right place when out-of-order chunks come in + self.stream_start = stream.tell() if parallel else None + + # download progress so far + self.progress_total = current_progress + + # encryption + self.encryption_options = encryption_options + + # parameters for each get operation + self.validate_content = validate_content + self.request_options = kwargs + + def _calculate_range(self, chunk_start): + if chunk_start + self.chunk_size > self.end_index: + chunk_end = self.end_index + else: + chunk_end = chunk_start + self.chunk_size + return chunk_start, chunk_end + + def get_chunk_offsets(self): + index = self.start_index + while index < self.end_index: + yield index + index += self.chunk_size + + async def process_chunk(self, chunk_start): + chunk_start, chunk_end = self._calculate_range(chunk_start) + chunk_data = await self._download_chunk(chunk_start, chunk_end) + length = chunk_end - chunk_start + if length > 0: + await self._write_to_stream(chunk_data, chunk_start) + await self._update_progress(length) + + async def yield_chunk(self, chunk_start): + chunk_start, chunk_end = self._calculate_range(chunk_start) + return await self._download_chunk(chunk_start, chunk_end) + + async def _update_progress(self, length): + if self.progress_lock: + async with self.progress_lock: + self.progress_total += length + else: + self.progress_total += length + + async def _write_to_stream(self, chunk_data, chunk_start): + if self.stream_lock: + async with self.stream_lock: + self.stream.seek(self.stream_start + (chunk_start - self.start_index)) + self.stream.write(chunk_data) + else: + self.stream.write(chunk_data) + + async def _download_chunk(self, chunk_start, chunk_end): + download_range, offset = process_range_and_offset( + chunk_start, chunk_end, chunk_end, self.encryption_options) + range_header, range_validation = validate_and_format_range_headers( + download_range[0], + download_range[1] - 1, + check_content_md5=self.validate_content) + + try: + _, response = await self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=self.total_size, + download_stream_current=self.progress_total, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + chunk_data = await process_content(response, offset[0], offset[1], self.encryption_options) + + # This makes sure that if_match is set so that we can validate + # that subsequent downloads are to an unmodified blob + if self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = response.properties.etag + + return chunk_data + +class StorageStreamDownloader(object): # pylint: disable=too-many-instance-attributes + """A streaming object to download from Azure Storage. + + The stream downloader can iterated, or download to open file or stream + over multiple threads. + """ + + def __init__( + self, service=None, + config=None, + offset=None, + length=None, + validate_content=None, + encryption_options=None, + **kwargs): + self.service = service + self.config = config + self.offset = offset + self.length = length + self.validate_content = validate_content + self.encryption_options = encryption_options or {} + self.request_options = kwargs + self.location_mode = None + self._download_complete = False + self._current_content = None + self._iter_downloader = None + self._iter_chunks = None + + # The service only provides transactional MD5s for chunks under 4MB. + # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first + # chunk so a transactional MD5 can be retrieved. + self.first_get_size = self.config.max_single_get_size if not self.validate_content \ + else self.config.max_chunk_get_size + initial_request_start = self.offset if self.offset is not None else 0 + if self.length is not None and self.length - self.offset < self.first_get_size: + initial_request_end = self.length + else: + initial_request_end = initial_request_start + self.first_get_size - 1 + + self.initial_range, self.initial_offset = process_range_and_offset( + initial_request_start, initial_request_end, self.length, self.encryption_options) + self.download_size = None + self.file_size = None + self.response = None + self.properties = None + + def __len__(self): + return self.download_size + + def __iter__(self): + raise TypeError("Async stream must be iterated asynchronously.") + + def __aiter__(self): + return self + + async def __anext__(self): + """Iterate through responses.""" + if self._current_content is None: + if self.download_size == 0: + self._current_content = b"" + else: + self._current_content = await process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + if not self._download_complete: + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + self._iter_downloader = _AsyncChunkDownloader( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=None, + parallel=False, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + self._iter_chunks = self._iter_downloader.get_chunk_offsets() + elif self._download_complete: + raise StopAsyncIteration("Download complete") + else: + try: + chunk = next(self._iter_chunks) + except StopIteration: + raise StopAsyncIteration("DownloadComplete") + self._current_content = await self._iter_downloader.yield_chunk(chunk) + + return self._current_content + + async def setup(self, extra_properties=None): + if self.response: + raise ValueError("Download stream already initialized.") + self.response = await self._initial_request() + self.properties = self.response.properties + + # Set the content length to the download size instead of the size of + # the last range + self.properties.size = self.download_size + + # Overwrite the content range to the user requested range + self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.file_size) + + # Set additional properties according to download type + if extra_properties: + for prop, value in extra_properties.items(): + setattr(self.properties, prop, value) + + # Overwrite the content MD5 as it is the MD5 for the last range instead + # of the stored MD5 + # TODO: Set to the stored MD5 when the service returns this + self.properties.content_md5 = None + + async def _initial_request(self): + range_header, range_validation = validate_and_format_range_headers( + self.initial_range[0], + self.initial_range[1], + start_range_required=False, + end_range_required=False, + check_content_md5=self.validate_content) + + try: + location_mode, response = await self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=None, + download_stream_current=0, + **self.request_options) + + # Check the location we read from to ensure we use the same one + # for subsequent requests. + self.location_mode = location_mode + + # Parse the total file size and adjust the download size if ranges + # were specified + self.file_size = parse_length_from_content_range(response.properties.content_range) + if self.length is not None: + # Use the length unless it is over the end of the file + self.download_size = min(self.file_size, self.length - self.offset + 1) + elif self.offset is not None: + self.download_size = self.file_size - self.offset + else: + self.download_size = self.file_size + + except HttpResponseError as error: + if self.offset is None and error.response.status_code == 416: + # Get range will fail on an empty file. If the user did not + # request a range, do a regular get request in order to get + # any properties. + try: + _, response = await self.service.download( + validate_content=self.validate_content, + data_stream_total=0, + download_stream_current=0, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + # Set the download size to empty + self.download_size = 0 + self.file_size = 0 + else: + process_storage_error(error) + + # If the file is small, the download is complete at this point. + # If file size is large, download the rest of the file in chunks. + if response.properties.size != self.download_size: + # Lock on the etag. This can be overriden by the user by specifying '*' + if self.request_options.get('modified_access_conditions'): + if not self.request_options['modified_access_conditions'].if_match: + self.request_options['modified_access_conditions'].if_match = response.properties.etag + else: + self._download_complete = True + return response + + async def content_as_bytes(self, max_connections=1): + """Download the contents of this file. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: bytes + """ + stream = BytesIO() + await self.download_to_stream(stream, max_connections=max_connections) + return stream.getvalue() + + async def content_as_text(self, max_connections=1, encoding='UTF-8'): + """Download the contents of this file, and decode as text. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: str + """ + content = await self.content_as_bytes(max_connections=max_connections) + return content.decode(encoding) + + async def download_to_stream(self, stream, max_connections=1): + """Download the contents of this file to a stream. + + :param stream: + The stream to download to. This can be an open file-handle, + or any writable stream. The stream must be seekable if the download + uses more than one parallel connection. + :returns: The properties of the downloaded file. + :rtype: Any + """ + if self._iter_downloader: + raise ValueError("Stream is currently being iterated.") + + # the stream must be seekable if parallel download is required + parallel = max_connections > 1 + if parallel: + error_message = "Target stream handle must be seekable." + if sys.version_info >= (3,) and not stream.seekable(): + raise ValueError(error_message) + + try: + stream.seek(stream.tell()) + except (NotImplementedError, AttributeError): + raise ValueError(error_message) + + if self.download_size == 0: + content = b"" + else: + content = await process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + # Write the content to the user stream + if content is not None: + stream.write(content) + if self._download_complete: + return self.properties + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader = _AsyncChunkDownloader( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + parallel=parallel, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + dl_tasks = downloader.get_chunk_offsets() + running_futures = [ + asyncio.ensure_future(downloader.process_chunk(d)) + for d in islice(dl_tasks, 0, max_connections) + ] + while running_futures: + # Wait for some download to finish before adding a new one + _done, running_futures = await asyncio.wait( + running_futures, return_when=asyncio.FIRST_COMPLETED) + try: + next_chunk = next(dl_tasks) + except StopIteration: + break + else: + running_futures.add(asyncio.ensure_future(downloader.process_chunk(next_chunk))) + + if running_futures: + # Wait for the remaining downloads to finish + await asyncio.wait(running_futures) + return self.properties diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/encryption.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/encryption.py index 222e213da627..3f25c8cf1e9c 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/encryption.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/encryption.py @@ -21,22 +21,17 @@ from azure.core.exceptions import HttpResponseError from ..version import VERSION -from .authentication import _encode_base64, _decode_base64_to_bytes +from . import encode_base64, decode_base64_to_bytes _ENCRYPTION_PROTOCOL_V1 = '1.0' -_ERROR_VALUE_NONE = '{0} should not be None.' _ERROR_OBJECT_INVALID = \ '{0} does not define a complete interface. Value of {1} is either missing or invalid.' -_ERROR_DATA_NOT_ENCRYPTED = 'Encryption required, but received data does not contain appropriate metatadata.' + \ - 'Data was either not encrypted or metadata has been lost.' -_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM = \ - 'Specified encryption algorithm is not supported.' def _validate_not_none(param_name, param): if param is None: - raise ValueError(_ERROR_VALUE_NONE.format(param_name)) + raise ValueError('{0} should not be None.'.format(param_name)) def _validate_key_encryption_key_wrap(kek): @@ -147,7 +142,7 @@ def _generate_encryption_data_dict(kek, cek, iv): # Use OrderedDict to comply with Java's ordering requirement. wrapped_content_key = OrderedDict() wrapped_content_key['KeyId'] = kek.get_kid() - wrapped_content_key['EncryptedKey'] = _encode_base64(wrapped_cek) + wrapped_content_key['EncryptedKey'] = encode_base64(wrapped_cek) wrapped_content_key['Algorithm'] = kek.get_key_wrap_algorithm() encryption_agent = OrderedDict() @@ -157,7 +152,7 @@ def _generate_encryption_data_dict(kek, cek, iv): encryption_data_dict = OrderedDict() encryption_data_dict['WrappedContentKey'] = wrapped_content_key encryption_data_dict['EncryptionAgent'] = encryption_agent - encryption_data_dict['ContentEncryptionIV'] = _encode_base64(iv) + encryption_data_dict['ContentEncryptionIV'] = encode_base64(iv) encryption_data_dict['KeyWrappingMetadata'] = {'EncryptionLibrary': 'Python ' + VERSION} return encryption_data_dict @@ -180,7 +175,7 @@ def _dict_to_encryption_data(encryption_data_dict): raise ValueError("Unsupported encryption version.") wrapped_content_key = encryption_data_dict['WrappedContentKey'] wrapped_content_key = _WrappedContentKey(wrapped_content_key['Algorithm'], - _decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), + decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), wrapped_content_key['KeyId']) encryption_agent = encryption_data_dict['EncryptionAgent'] @@ -192,7 +187,7 @@ def _dict_to_encryption_data(encryption_data_dict): else: key_wrapping_metadata = None - encryption_data = _EncryptionData(_decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), + encryption_data = _EncryptionData(decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), encryption_agent, wrapped_content_key, key_wrapping_metadata) @@ -259,7 +254,49 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol return content_encryption_key -def _encrypt_blob(blob, key_encryption_key): +def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver=None): + ''' + Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. + Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). + Returns the original plaintex. + + :param str message: + The ciphertext to be decrypted. + :param _EncryptionData encryption_data: + The metadata associated with this ciphertext. + :param object key_encryption_key: + The user-provided key-encryption-key. Must implement the following methods: + unwrap_key(key, algorithm) + - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. + get_kid() + - returns a string key id for this key-encryption-key. + :param function resolver(kid): + The user-provided key resolver. Uses the kid string to return a key-encryption-key + implementing the interface defined above. + :return: The decrypted plaintext. + :rtype: str + ''' + _validate_not_none('message', message) + content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) + + if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: + raise ValueError('Specified encryption algorithm is not supported.') + + cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) + + # decrypt data + decrypted_data = message + decryptor = cipher.decryptor() + decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) + + # unpad data + unpadder = PKCS7(128).unpadder() + decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) + + return decrypted_data + + +def encrypt_blob(blob, key_encryption_key): ''' Encrypts the given blob using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -302,7 +339,7 @@ def _encrypt_blob(blob, key_encryption_key): return dumps(encryption_data), encrypted_data -def _generate_blob_encryption_data(key_encryption_key): +def generate_blob_encryption_data(key_encryption_key): ''' Generates the encryption_metadata for the blob. @@ -328,8 +365,8 @@ def _generate_blob_encryption_data(key_encryption_key): return content_encryption_key, initialization_vector, encryption_data -def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, - response, start_offset, end_offset): +def decrypt_blob(require_encryption, key_encryption_key, key_resolver, + content, start_offset, end_offset, response_headers): ''' Decrypts the given blob contents and returns only the requested range. @@ -346,29 +383,25 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, :return: The decrypted blob content. :rtype: bytes ''' - if response is None: - raise ValueError("Response cannot be None.") - content = b"".join(list(response)) - if not content: - return content - try: - encryption_data = _dict_to_encryption_data(loads(response.response.headers['x-ms-meta-encryptiondata'])) + encryption_data = _dict_to_encryption_data(loads(response_headers['x-ms-meta-encryptiondata'])) except: # pylint: disable=bare-except if require_encryption: - raise ValueError(_ERROR_DATA_NOT_ENCRYPTED) + raise ValueError( + 'Encryption required, but received data does not contain appropriate metatadata.' + \ + 'Data was either not encrypted or metadata has been lost.') return content if encryption_data.encryption_agent.encryption_algorithm != _EncryptionAlgorithm.AES_CBC_256: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) + raise ValueError('Specified encryption algorithm is not supported.') - blob_type = response.response.headers['x-ms-blob-type'] + blob_type = response_headers['x-ms-blob-type'] iv = None unpad = False - if 'content-range' in response.response.headers: - content_range = response.response.headers['content-range'] + if 'content-range' in response_headers: + content_range = response_headers['content-range'] # Format: 'bytes x-y/size' # Ignore the word 'bytes' @@ -407,7 +440,7 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, return content[start_offset: len(content) - end_offset] -def _get_blob_encryptor_and_padder(cek, iv, should_pad): +def get_blob_encryptor_and_padder(cek, iv, should_pad): encryptor = None padder = None @@ -419,7 +452,7 @@ def _get_blob_encryptor_and_padder(cek, iv, should_pad): return encryptor, padder -def _encrypt_queue_message(message, key_encryption_key): +def encrypt_queue_message(message, key_encryption_key): ''' Encrypts the given plain text message using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -459,7 +492,7 @@ def _encrypt_queue_message(message, key_encryption_key): encrypted_data = encryptor.update(padded_data) + encryptor.finalize() # Build the dictionary structure. - queue_message = {'EncryptedMessageContents': _encode_base64(encrypted_data), + queue_message = {'EncryptedMessageContents': encode_base64(encrypted_data), 'EncryptionData': _generate_encryption_data_dict(key_encryption_key, content_encryption_key, initialization_vector)} @@ -467,7 +500,7 @@ def _encrypt_queue_message(message, key_encryption_key): return dumps(queue_message) -def _decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): +def decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): ''' Returns the decrypted message contents from an EncryptedQueueMessage. If no encryption metadata is present, will return the unaltered message. @@ -492,7 +525,7 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption message = loads(message) encryption_data = _dict_to_encryption_data(message['EncryptionData']) - decoded_data = _decode_base64_to_bytes(message['EncryptedMessageContents']) + decoded_data = decode_base64_to_bytes(message['EncryptedMessageContents']) except (KeyError, ValueError): # Message was not json formatted and so was not encrypted # or the user provided a json formatted message. @@ -501,51 +534,9 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption return message try: - return _decrypt(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') + return _decrypt_message(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') except Exception as error: raise HttpResponseError( message="Decryption failed.", response=response, error=error) - - -def _decrypt(message, encryption_data, key_encryption_key=None, resolver=None): - ''' - Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. - Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). - Returns the original plaintex. - - :param str message: - The ciphertext to be decrypted. - :param _EncryptionData encryption_data: - The metadata associated with this ciphertext. - :param object key_encryption_key: - The user-provided key-encryption-key. Must implement the following methods: - unwrap_key(key, algorithm) - - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. - get_kid() - - returns a string key id for this key-encryption-key. - :param function resolver(kid): - The user-provided key resolver. Uses the kid string to return a key-encryption-key - implementing the interface defined above. - :return: The decrypted plaintext. - :rtype: str - ''' - _validate_not_none('message', message) - content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) - - if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) - - cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) - - # decrypt data - decrypted_data = message - decryptor = cipher.decryptor() - decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) - - # unpad data - unpadder = PKCS7(128).unpadder() - decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) - - return decrypted_data 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 3fcd33a1fa69..7185141649f9 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 @@ -210,30 +210,6 @@ def get(self, key, default=None): return default -class ModifiedAccessConditions(object): - """Additional parameters for a set of operations. - - :param if_modified_since: Specify this header value to operate only on a - blob if it has been modified since the specified date/time. - :type if_modified_since: datetime - :param if_unmodified_since: Specify this header value to operate only on a - blob if it has not been modified since the specified date/time. - :type if_unmodified_since: datetime - :param if_match: Specify an ETag value to operate only on blobs with a - matching value. - :type if_match: str - :param if_none_match: Specify an ETag value to operate only on blobs - without a matching value. - :type if_none_match: str - """ - - def __init__(self, **kwargs): - self.if_modified_since = kwargs.get('if_modified_since', None) - self.if_unmodified_since = kwargs.get('if_unmodified_since', None) - self.if_match = kwargs.get('if_match', None) - self.if_none_match = kwargs.get('if_none_match', None) - - class LocationMode(object): """ Specifies the location the request should be sent to. This mode only applies @@ -403,32 +379,31 @@ def __str__(self): class Services(object): - """ - Specifies the services accessible with the account SAS. + """Specifies the services accessible with the account SAS. :cvar Services Services.BLOB: The blob service. :cvar Services Services.FILE: The file service :cvar Services Services.QUEUE: The queue service. - :cvar Services Services.TABLE: The table service. :param bool blob: - Access to any blob service, for example, the `.BlockBlobService` + Access for the `~azure.storage.blob.blob_service_client.BlobServiceClient` :param bool queue: - Access to the `.QueueService` + Access for the `~azure.storage.queue.queue_service_client.QueueServiceClient` :param bool file: - Access to the `.FileService` - :param bool table: - Access to the TableService + Access for the `~azure.storage.file.file_service_client.FileServiceClient` :param str _str: A string representing the services. """ - def __init__(self, blob=False, queue=False, file=False, table=False, _str=None): + BLOB = None # type: Services + QUEUE = None # type: Services + FILE = None # type: Services + + def __init__(self, blob=False, queue=False, file=False, _str=None): if not _str: _str = '' self.blob = blob or ('b' in _str) self.queue = queue or ('q' in _str) self.file = file or ('f' in _str) - self.table = table or ('t' in _str) def __or__(self, other): return Services(_str=str(self) + str(other)) @@ -439,11 +414,9 @@ def __add__(self, other): def __str__(self): return (('b' if self.blob else '') + ('q' if self.queue else '') + - ('t' if self.table else '') + ('f' if self.file else '')) -Services.BLOB = Services(blob=True) # type: ignore -Services.QUEUE = Services(queue=True) # type: ignore -Services.TABLE = Services(table=True) # type: ignore -Services.FILE = Services(file=True) # type: ignore +Services.BLOB = Services(blob=True) +Services.QUEUE = Services(queue=True) +Services.FILE = Services(file=True) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies.py index 8e1ad4152ef0..f3e7c182d37a 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies.py @@ -47,11 +47,13 @@ except NameError: _unicode_type = str - -_LOGGER = logging.getLogger(__name__) if TYPE_CHECKING: from azure.core.pipeline import PipelineRequest, PipelineResponse + +_LOGGER = logging.getLogger(__name__) + + def encode_base64(data): if isinstance(data, _unicode_type): data = data.encode('utf-8') @@ -59,6 +61,20 @@ def encode_base64(data): return encoded.decode('utf-8') +def is_exhausted(settings): + """Are we out of retries?""" + retry_counts = (settings['total'], settings['connect'], settings['read'], settings['status']) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + return min(retry_counts) < 0 + + +def retry_hook(settings, **kwargs): + if settings['hook']: + settings['hook'](retry_count=settings['count'] - 1, location_mode=settings['mode'], **kwargs) + + def is_retry(response, mode): """Is this method/status code retryable? (Based on whitelists and control variables such as the number of total retries to allow, whether to @@ -102,28 +118,6 @@ def on_request(self, request, **kwargs): message_id) -class StorageDataSettings(object): - - def __init__(self, **kwargs): - self.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) - self.copy_polling_interval = 15 - - # Block blob uploads - self.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) - self.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) - self.use_byte_buffer = kwargs.get('use_byte_buffer', False) - - # Page blob uploads - self.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) - - # Blob downloads - self.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) - self.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) - - # File uploads - self.max_range_size = kwargs.get('max_range_size', 4 * 1024 * 1024) - - class StorageHeadersPolicy(HeadersPolicy): def on_request(self, request, **kwargs): @@ -254,7 +248,9 @@ class StorageUserAgentPolicy(SansIOHTTPPolicy): def __init__(self, **kwargs): self._application = kwargs.pop('user_agent', None) - self._user_agent = "azsdk-python-storage-file/{} Python/{} ({})".format( + storage_sdk = kwargs.pop('storage_sdk') + self._user_agent = "azsdk-python-storage-{}/{} Python/{} ({})".format( + storage_sdk, VERSION, platform.python_version(), platform.platform()) @@ -446,15 +442,6 @@ def sleep(self, settings, transport): return transport.sleep(backoff) - def is_exhausted(self, settings): # pylint: disable=no-self-use - """Are we out of retries?""" - retry_counts = (settings['total'], settings['connect'], settings['read'], settings['status']) - retry_counts = list(filter(None, retry_counts)) - if not retry_counts: - return False - - return min(retry_counts) < 0 - def increment(self, settings, request, response=None, error=None): """Increment the retry counters. @@ -485,7 +472,7 @@ def increment(self, settings, request, response=None, error=None): settings['status'] -= 1 settings['history'].append(RequestHistory(request, http_response=response)) - if not self.is_exhausted(settings): + if not is_exhausted(settings): if request.method not in ['PUT'] and settings['retry_secondary']: self._set_next_host_location(settings, request) @@ -500,13 +487,6 @@ def increment(self, settings, request, response=None, error=None): except UnsupportedOperation: # if body is not seekable, then retry would not work return False - if settings['hook']: - settings['hook']( - request=request, - response=response, - error=error, - retry_count=settings['count'], - location_mode=settings['mode']) settings['count'] += 1 return True return False @@ -524,14 +504,23 @@ def send(self, request): request=request.http_request, response=response.http_response) if retries_remaining: + retry_hook( + retry_settings, + request=request.http_request, + response=response.http_response, + error=None) self.sleep(retry_settings, request.context.transport) - continue break except AzureError as err: retries_remaining = self.increment( retry_settings, request=request.http_request, error=err) if retries_remaining: + retry_hook( + retry_settings, + request=request.http_request, + response=None, + error=err) self.sleep(retry_settings, request.context.transport) continue raise err diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies_async.py new file mode 100644 index 000000000000..b84ba562b948 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/policies_async.py @@ -0,0 +1,229 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import asyncio +import random +import logging +from typing import Any, TYPE_CHECKING + +from azure.core.pipeline.policies import AsyncHTTPPolicy +from azure.core.exceptions import AzureError + +from .policies import is_retry, StorageRetryPolicy + +if TYPE_CHECKING: + from azure.core.pipeline import PipelineRequest, PipelineResponse + + +_LOGGER = logging.getLogger(__name__) + + +async def retry_hook(settings, **kwargs): + if settings['hook']: + if asyncio.iscoroutine(settings['hook']): + await settings['hook']( + retry_count=settings['count'] - 1, + location_mode=settings['mode'], + **kwargs) + else: + settings['hook']( + retry_count=settings['count'] - 1, + location_mode=settings['mode'], + **kwargs) + + +class AsyncStorageResponseHook(AsyncHTTPPolicy): + + def __init__(self, **kwargs): # pylint: disable=unused-argument + self._response_callback = kwargs.get('raw_response_hook') + super(AsyncStorageResponseHook, self).__init__() + + async def send(self, request): + # type: (PipelineRequest) -> PipelineResponse + data_stream_total = request.context.get('data_stream_total') or \ + request.context.options.pop('data_stream_total', None) + download_stream_current = request.context.get('download_stream_current') or \ + request.context.options.pop('download_stream_current', None) + upload_stream_current = request.context.get('upload_stream_current') or \ + request.context.options.pop('upload_stream_current', None) + response_callback = request.context.get('response_callback') or \ + request.context.options.pop('raw_response_hook', self._response_callback) + + response = await self.next.send(request) + await response.http_response.load_body() + response.http_response.internal_response.body = response.http_response.body() + + will_retry = is_retry(response, request.context.options.get('mode')) + if not will_retry and download_stream_current is not None: + download_stream_current += int(response.http_response.headers.get('Content-Length', 0)) + if data_stream_total is None: + content_range = response.http_response.headers.get('Content-Range') + if content_range: + data_stream_total = int(content_range.split(' ', 1)[1].split('/', 1)[1]) + else: + data_stream_total = download_stream_current + elif not will_retry and upload_stream_current is not None: + upload_stream_current += int(response.http_request.headers.get('Content-Length', 0)) + for pipeline_obj in [request, response]: + pipeline_obj.context['data_stream_total'] = data_stream_total + pipeline_obj.context['download_stream_current'] = download_stream_current + pipeline_obj.context['upload_stream_current'] = upload_stream_current + if response_callback: + if asyncio.iscoroutine(response_callback): + await response_callback(response) + else: + response_callback(response) + request.context['response_callback'] = response_callback + return response + +class AsyncStorageRetryPolicy(StorageRetryPolicy): + """ + The base class for Exponential and Linear retries containing shared code. + """ + + async def sleep(self, settings, transport): + backoff = self.get_backoff_time(settings) + if not backoff or backoff < 0: + return + await transport.sleep(backoff) + + async def send(self, request): + retries_remaining = True + response = None + retry_settings = self.configure_retries(request) + while retries_remaining: + try: + response = await self.next.send(request) + if is_retry(response, retry_settings['mode']): + retries_remaining = self.increment( + retry_settings, + request=request.http_request, + response=response.http_response) + if retries_remaining: + await retry_hook( + retry_settings, + request=request.http_request, + response=response.http_response, + error=None) + await self.sleep(retry_settings, request.context.transport) + continue + break + except AzureError as err: + retries_remaining = self.increment( + retry_settings, request=request.http_request, error=err) + if retries_remaining: + await retry_hook( + retry_settings, + request=request.http_request, + response=None, + error=err) + await self.sleep(retry_settings, request.context.transport) + continue + raise err + if retry_settings['history']: + response.context['history'] = retry_settings['history'] + response.http_response.location_mode = retry_settings['mode'] + return response + + +class NoRetry(AsyncStorageRetryPolicy): + + def __init__(self): + super(NoRetry, self).__init__(retry_total=0) + + def increment(self, *args, **kwargs): # pylint: disable=unused-argument,arguments-differ + return False + + +class ExponentialRetry(AsyncStorageRetryPolicy): + """Exponential retry.""" + + def __init__(self, initial_backoff=15, increment_base=3, retry_total=3, + retry_to_secondary=False, random_jitter_range=3, **kwargs): + ''' + Constructs an Exponential retry object. The initial_backoff is used for + the first retry. Subsequent retries are retried after initial_backoff + + increment_power^retry_count seconds. For example, by default the first retry + occurs after 15 seconds, the second after (15+3^1) = 18 seconds, and the + third after (15+3^2) = 24 seconds. + + :param int initial_backoff: + The initial backoff interval, in seconds, for the first retry. + :param int increment_base: + The base, in seconds, to increment the initial_backoff by after the + first retry. + :param int max_attempts: + The maximum number of retry attempts. + :param bool retry_to_secondary: + Whether the request should be retried to secondary, if able. This should + only be enabled of RA-GRS accounts are used and potentially stale data + can be handled. + :param int random_jitter_range: + A number in seconds which indicates a range to jitter/randomize for the back-off interval. + For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3. + ''' + self.initial_backoff = initial_backoff + self.increment_base = increment_base + self.random_jitter_range = random_jitter_range + super(ExponentialRetry, self).__init__( + retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) + + def get_backoff_time(self, settings): + """ + Calculates how long to sleep before retrying. + + :return: + An integer indicating how long to wait before retrying the request, + or None to indicate no retry should be performed. + :rtype: int or None + """ + random_generator = random.Random() + backoff = self.initial_backoff + (0 if settings['count'] == 0 else pow(self.increment_base, settings['count'])) + random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0 + random_range_end = backoff + self.random_jitter_range + return random_generator.uniform(random_range_start, random_range_end) + + +class LinearRetry(AsyncStorageRetryPolicy): + """Linear retry.""" + + def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_jitter_range=3, **kwargs): + """ + Constructs a Linear retry object. + + :param int backoff: + The backoff interval, in seconds, between retries. + :param int max_attempts: + The maximum number of retry attempts. + :param bool retry_to_secondary: + Whether the request should be retried to secondary, if able. This should + only be enabled of RA-GRS accounts are used and potentially stale data + can be handled. + :param int random_jitter_range: + A number in seconds which indicates a range to jitter/randomize for the back-off interval. + For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3. + """ + self.backoff = backoff + self.random_jitter_range = random_jitter_range + super(LinearRetry, self).__init__( + retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs) + + def get_backoff_time(self, settings): + """ + Calculates how long to sleep before retrying. + + :return: + An integer indicating how long to wait before retrying the request, + or None to indicate no retry should be performed. + :rtype: int or None + """ + random_generator = random.Random() + # the backoff interval normally does not change, however there is the possibility + # that it was modified by accessing the property directly after initializing the object + random_range_start = self.backoff - self.random_jitter_range \ + if self.backoff > self.random_jitter_range else 0 + random_range_end = self.backoff + self.random_jitter_range + return random_generator.uniform(random_range_start, random_range_end) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/request_handlers.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/request_handlers.py new file mode 100644 index 000000000000..cd5e4848633d --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/request_handlers.py @@ -0,0 +1,144 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) + +import logging +from os import fstat +from io import (SEEK_END, SEEK_SET, UnsupportedOperation) + +import isodate + +from azure.core import Configuration +from azure.core.exceptions import raise_with_traceback +from azure.core.pipeline import Pipeline + + +_LOGGER = logging.getLogger(__name__) + + +def serialize_iso(attr): + """Serialize Datetime object into ISO-8601 formatted string. + + :param Datetime attr: Object to be serialized. + :rtype: str + :raises: ValueError if format invalid. + """ + if not attr: + return None + if isinstance(attr, str): + attr = isodate.parse_datetime(attr) + try: + utc = attr.utctimetuple() + if utc.tm_year > 9999 or utc.tm_year < 1: + raise OverflowError("Hit max or min date") + + date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( + utc.tm_year, utc.tm_mon, utc.tm_mday, + utc.tm_hour, utc.tm_min, utc.tm_sec) + return date + 'Z' + except (ValueError, OverflowError) as err: + msg = "Unable to serialize datetime object." + raise_with_traceback(ValueError, msg, err) + except AttributeError as err: + msg = "ISO-8601 object must be valid Datetime object." + raise_with_traceback(TypeError, msg, err) + + +def get_length(data): + length = None + # Check if object implements the __len__ method, covers most input cases such as bytearray. + try: + length = len(data) + except: # pylint: disable=bare-except + pass + + if not length: + # Check if the stream is a file-like stream object. + # If so, calculate the size using the file descriptor. + try: + fileno = data.fileno() + except (AttributeError, UnsupportedOperation): + pass + else: + return fstat(fileno).st_size + + # If the stream is seekable and tell() is implemented, calculate the stream size. + try: + current_position = data.tell() + data.seek(0, SEEK_END) + length = data.tell() - current_position + data.seek(current_position, SEEK_SET) + except (AttributeError, UnsupportedOperation): + pass + + return length + + +def read_length(data): + try: + if hasattr(data, 'read'): + read_data = b'' + for chunk in iter(lambda: data.read(4096), b""): + read_data += chunk + return len(read_data), read_data + if hasattr(data, '__iter__'): + read_data = b'' + for chunk in data: + read_data += chunk + return len(read_data), read_data + except: # pylint: disable=bare-except + pass + raise ValueError("Unable to calculate content length, please specify.") + + +def validate_and_format_range_headers( + start_range, end_range, start_range_required=True, + end_range_required=True, check_content_md5=False, align_to_page=False): + # If end range is provided, start range must be provided + if (start_range_required or end_range is not None) and start_range is None: + raise ValueError("start_range value cannot be None.") + if end_range_required and end_range is None: + raise ValueError("end_range value cannot be None.") + + # Page ranges must be 512 aligned + if align_to_page: + if start_range is not None and start_range % 512 != 0: + raise ValueError("Invalid page blob start_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(start_range)) + if end_range is not None and end_range % 512 != 511: + raise ValueError("Invalid page blob end_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(end_range)) + + # Format based on whether end_range is present + range_header = None + if end_range is not None: + range_header = 'bytes={0}-{1}'.format(start_range, end_range) + elif start_range is not None: + range_header = "bytes={0}-".format(start_range) + + # Content MD5 can only be provided for a complete range less than 4MB in size + range_validation = None + if check_content_md5: + if start_range is None or end_range is None: + raise ValueError("Both start and end range requied for MD5 content validation.") + if end_range - start_range > 4 * 1024 * 1024: + raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") + range_validation = 'true' + + return range_header, range_validation + + +def add_metadata_headers(metadata=None): + # type: (Optional[Dict[str, str]]) -> Dict[str, str] + headers = {} + if metadata: + for key, value in metadata.items(): + headers['x-ms-meta-{}'.format(key)] = value + return headers 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 new file mode 100644 index 000000000000..472399264aa9 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/response_handlers.py @@ -0,0 +1,132 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging + +from azure.core.pipeline.policies import ContentDecodePolicy +from azure.core.exceptions import ( + HttpResponseError, + ResourceNotFoundError, + ResourceModifiedError, + ResourceExistsError, + ClientAuthenticationError, + DecodeError) + +from .models import StorageErrorCode + + +if TYPE_CHECKING: + from datetime import datetime + from azure.core.exceptions import AzureError + + +_LOGGER = logging.getLogger(__name__) + + +def parse_length_from_content_range(content_range): + ''' + Parses the blob length from the content range header: bytes 1-3/65537 + ''' + if content_range is None: + return None + + # First, split in space and take the second half: '1-3/65537' + # Next, split on slash and take the second half: '65537' + # Finally, convert to an int: 65537 + return int(content_range.split(' ', 1)[1].split('/', 1)[1]) + + +def normalize_headers(headers): + normalized = {} + for key, value in headers.items(): + if key.startswith('x-ms-'): + key = key[5:] + normalized[key.lower().replace('-', '_')] = value + return normalized + + +def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argument + raw_metadata = {k: v for k, v in response.headers.items() if k.startswith("x-ms-meta-")} + return {k[10:]: v for k, v in raw_metadata.items()} + + +def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers) + + +def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers), deserialized + + +def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return response.location_mode, deserialized + + +def process_storage_error(storage_error): + raise_error = HttpResponseError + error_code = storage_error.response.headers.get('x-ms-error-code') + error_message = storage_error.message + additional_data = {} + try: + error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) + if error_body: + for info in error_body.iter(): + if info.tag.lower() == 'code': + error_code = info.text + elif info.tag.lower() == 'message': + error_message = info.text + else: + additional_data[info.tag] = info.text + except DecodeError: + pass + + try: + if error_code: + error_code = StorageErrorCode(error_code) + if error_code in [StorageErrorCode.condition_not_met, + StorageErrorCode.blob_overwritten]: + raise_error = ResourceModifiedError + if error_code in [StorageErrorCode.invalid_authentication_info, + StorageErrorCode.authentication_failed]: + raise_error = ClientAuthenticationError + if error_code in [StorageErrorCode.resource_not_found, + StorageErrorCode.blob_not_found, + StorageErrorCode.queue_not_found, + StorageErrorCode.container_not_found, + StorageErrorCode.parent_not_found, + StorageErrorCode.share_not_found]: + raise_error = ResourceNotFoundError + if error_code in [StorageErrorCode.account_already_exists, + StorageErrorCode.account_being_created, + StorageErrorCode.resource_already_exists, + StorageErrorCode.resource_type_mismatch, + StorageErrorCode.blob_already_exists, + StorageErrorCode.queue_already_exists, + StorageErrorCode.container_already_exists, + StorageErrorCode.container_being_deleted, + StorageErrorCode.queue_being_deleted, + StorageErrorCode.share_already_exists, + StorageErrorCode.share_being_deleted]: + raise_error = ResourceExistsError + except ValueError: + # Got an unknown error code + pass + + try: + error_message += "\nErrorCode:{}".format(error_code.value) + except AttributeError: + error_message += "\nErrorCode:{}".format(error_code) + for name, info in additional_data.items(): + error_message += "\n{}:{}".format(name, info) + + error = raise_error(message=error_message, response=storage_error.response) + error.error_code = error_code + error.additional_info = additional_data + raise error diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/shared_access_signature.py index 8802ad905270..16ff778c5c1e 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 @@ -8,7 +8,7 @@ from datetime import date from .constants import X_MS_VERSION -from .utils import _sign_string, url_quote, _QueryStringConstants +from . import sign_string, url_quote if sys.version_info < (3,): @@ -25,6 +25,54 @@ def _to_utc_datetime(value): return value.strftime('%Y-%m-%dT%H:%M:%SZ') +class QueryStringConstants(object): + SIGNED_SIGNATURE = 'sig' + SIGNED_PERMISSION = 'sp' + SIGNED_START = 'st' + SIGNED_EXPIRY = 'se' + SIGNED_RESOURCE = 'sr' + SIGNED_IDENTIFIER = 'si' + SIGNED_IP = 'sip' + SIGNED_PROTOCOL = 'spr' + SIGNED_VERSION = 'sv' + SIGNED_CACHE_CONTROL = 'rscc' + SIGNED_CONTENT_DISPOSITION = 'rscd' + SIGNED_CONTENT_ENCODING = 'rsce' + SIGNED_CONTENT_LANGUAGE = 'rscl' + SIGNED_CONTENT_TYPE = 'rsct' + START_PK = 'spk' + START_RK = 'srk' + END_PK = 'epk' + END_RK = 'erk' + SIGNED_RESOURCE_TYPES = 'srt' + SIGNED_SERVICES = 'ss' + + @staticmethod + def to_list(): + return [ + QueryStringConstants.SIGNED_SIGNATURE, + QueryStringConstants.SIGNED_PERMISSION, + QueryStringConstants.SIGNED_START, + QueryStringConstants.SIGNED_EXPIRY, + QueryStringConstants.SIGNED_RESOURCE, + QueryStringConstants.SIGNED_IDENTIFIER, + QueryStringConstants.SIGNED_IP, + QueryStringConstants.SIGNED_PROTOCOL, + QueryStringConstants.SIGNED_VERSION, + QueryStringConstants.SIGNED_CACHE_CONTROL, + QueryStringConstants.SIGNED_CONTENT_DISPOSITION, + QueryStringConstants.SIGNED_CONTENT_ENCODING, + QueryStringConstants.SIGNED_CONTENT_LANGUAGE, + QueryStringConstants.SIGNED_CONTENT_TYPE, + QueryStringConstants.START_PK, + QueryStringConstants.START_RK, + QueryStringConstants.END_PK, + QueryStringConstants.END_RK, + QueryStringConstants.SIGNED_RESOURCE_TYPES, + QueryStringConstants.SIGNED_SERVICES, + ] + + class SharedAccessSignature(object): ''' Provides a factory for creating account access @@ -112,33 +160,33 @@ def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): 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) + 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) + self._add_query(QueryStringConstants.SIGNED_RESOURCE, resource) def add_id(self, policy_id): - self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, 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) + 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) + 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): @@ -153,29 +201,29 @@ def get_value_to_append(query): # 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) + + (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_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)) + (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)) + 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): @@ -184,17 +232,17 @@ def get_value_to_append(query): 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)) + 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]) @@ -451,21 +499,21 @@ def get_value_to_append(query): # 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) + + (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_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)) + self._add_query(QueryStringConstants.SIGNED_SIGNATURE, + sign_string(account_key, string_to_sign)) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/upload_chunking.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads.py similarity index 56% rename from sdk/storage/azure-storage-file/azure/storage/file/_shared/upload_chunking.py rename to sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads.py index 4ab2864aea5e..2b269fb1d0ba 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/upload_chunking.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads.py @@ -5,113 +5,84 @@ # -------------------------------------------------------------------------- # pylint: disable=no-self-use +from concurrent import futures from io import (BytesIO, IOBase, SEEK_CUR, SEEK_END, SEEK_SET, UnsupportedOperation) from threading import Lock - +from itertools import islice from math import ceil import six -from .models import ModifiedAccessConditions -from .utils import ( - encode_base64, - url_quote, - get_length, - return_response_headers) -from .encryption import _get_blob_encryptor_and_padder +from . import encode_base64, url_quote +from .request_handlers import get_length +from .response_handlers import return_response_headers +from .encryption import get_blob_encryptor_and_padder _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024 _ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.' -def upload_file_chunks(file_service, file_size, block_size, stream, max_connections, - validate_content, timeout, **kwargs): - uploader = FileChunkUploader( - file_service, - file_size, - block_size, - stream, - max_connections > 1, - validate_content, - timeout, - **kwargs - ) - if max_connections > 1: - import concurrent.futures - executor = concurrent.futures.ThreadPoolExecutor(max_connections) - range_ids = list(executor.map(uploader.process_chunk, uploader.get_chunk_offsets())) - else: - if file_size is not None: - range_ids = [uploader.process_chunk(start) for start in uploader.get_chunk_offsets()] +def _parallel_uploads(executor, uploader, pending, running): + range_ids = [] + while True: + # Wait for some download to finish before adding a new one + done, running = futures.wait(running, return_when=futures.FIRST_COMPLETED) + range_ids.extend([chunk.result() for chunk in done]) + try: + next_chunk = next(pending) + except StopIteration: + break else: - range_ids = uploader.process_all_unknown_size() - return range_ids + running.add(executor.submit(uploader.process_chunk, next_chunk)) + # Wait for the remaining uploads to finish + done, _running = futures.wait(running) + range_ids.extend([chunk.result() for chunk in done]) + return range_ids -def upload_blob_chunks(blob_service, blob_size, block_size, stream, max_connections, validate_content, # pylint: disable=too-many-locals - access_conditions, uploader_class, append_conditions=None, modified_access_conditions=None, - timeout=None, content_encryption_key=None, initialization_vector=None, **kwargs): - encryptor, padder = _get_blob_encryptor_and_padder( - content_encryption_key, - initialization_vector, - uploader_class is not PageBlobChunkUploader) +def upload_data_chunks( + service=None, + uploader_class=None, + total_size=None, + chunk_size=None, + max_connections=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + + if encryption_options: + encryptor, padder = get_blob_encryptor_and_padder( + encryption_options.get('key'), + encryption_options.get('vector'), + uploader_class is not PageBlobChunkUploader) + kwargs['encryptor'] = encryptor + kwargs['padder'] = padder + + parallel = max_connections > 1 + if parallel and 'modified_access_conditions' in kwargs: + # Access conditions do not work with parallelism + kwargs['modified_access_conditions'] = None uploader = uploader_class( - blob_service, - blob_size, - block_size, - stream, - max_connections > 1, - validate_content, - access_conditions, - append_conditions, - timeout, - encryptor, - padder, - **kwargs - ) - - # Access conditions do not work with parallelism - if max_connections > 1: - uploader.modified_access_conditions = None - else: - uploader.modified_access_conditions = modified_access_conditions - - if max_connections > 1: - import concurrent.futures - from threading import BoundedSemaphore - - # Ensures we bound the chunking so we only buffer and submit 'max_connections' - # amount of work items to the executor. This is necessary as the executor queue will keep - # accepting submitted work items, which results in buffering all the blocks if - # the max_connections + 1 ensures the next chunk is already buffered and ready for when - # the worker thread is available. - chunk_throttler = BoundedSemaphore(max_connections + 1) - - executor = concurrent.futures.ThreadPoolExecutor(max_connections) - futures = [] - running_futures = [] - - # Check for exceptions and fail fast. - for chunk in uploader.get_chunk_streams(): - for f in running_futures: - if f.done(): - if f.exception(): - raise f.exception() - running_futures.remove(f) - - chunk_throttler.acquire() - future = executor.submit(uploader.process_chunk, chunk) - - # Calls callback upon completion (even if the callback was added after the Future task is done). - future.add_done_callback(lambda x: chunk_throttler.release()) - futures.append(future) - running_futures.append(future) - - # result() will wait until completion and also raise any exceptions that may have been set. - range_ids = [f.result() for f in futures] + service=service, + total_size=total_size, + chunk_size=chunk_size, + stream=stream, + parallel=parallel, + validate_content=validate_content, + **kwargs) + + if parallel: + executor = futures.ThreadPoolExecutor(max_connections) + upload_tasks = uploader.get_chunk_streams() + running_futures = [ + executor.submit(uploader.process_chunk, u) + for u in islice(upload_tasks, 0, max_connections) + ] + range_ids = _parallel_uploads(executor, uploader, upload_tasks, running_futures) else: range_ids = [uploader.process_chunk(result) for result in uploader.get_chunk_streams()] @@ -120,59 +91,55 @@ def upload_blob_chunks(blob_service, blob_size, block_size, stream, max_connecti return uploader.response_headers -def upload_blob_substream_blocks(blob_service, blob_size, block_size, stream, max_connections, - validate_content, access_conditions, uploader_class, - append_conditions=None, modified_access_conditions=None, timeout=None, **kwargs): - +def upload_substream_blocks( + service=None, + uploader_class=None, + total_size=None, + chunk_size=None, + max_connections=None, + stream=None, + **kwargs): + parallel = max_connections > 1 + if parallel and 'modified_access_conditions' in kwargs: + # Access conditions do not work with parallelism + kwargs['modified_access_conditions'] = None uploader = uploader_class( - blob_service, - blob_size, - block_size, - stream, - max_connections > 1, - validate_content, - access_conditions, - append_conditions, - timeout, - None, - None, - **kwargs - ) - # ETag matching does not work with parallelism as a ranged upload may start - # before the previous finishes and provides an etag - if max_connections > 1: - uploader.modified_access_conditions = None - else: - uploader.modified_access_conditions = modified_access_conditions - - if max_connections > 1: - import concurrent.futures - executor = concurrent.futures.ThreadPoolExecutor(max_connections) - range_ids = list(executor.map(uploader.process_substream_block, uploader.get_substream_blocks())) - else: - range_ids = [uploader.process_substream_block(result) for result in uploader.get_substream_blocks()] - - return range_ids - - -class _BlobChunkUploader(object): # pylint: disable=too-many-instance-attributes - - def __init__(self, blob_service, blob_size, chunk_size, stream, parallel, validate_content, - access_conditions, append_conditions, timeout, encryptor, padder, **kwargs): - self.blob_service = blob_service - self.blob_size = blob_size + service=service, + total_size=total_size, + chunk_size=chunk_size, + stream=stream, + parallel=parallel, + **kwargs) + + if parallel: + executor = futures.ThreadPoolExecutor(max_connections) + upload_tasks = uploader.get_substream_blocks() + running_futures = [ + executor.submit(uploader.process_substream_block, u) + for u in islice(upload_tasks, 0, max_connections) + ] + return _parallel_uploads(executor, uploader, upload_tasks, running_futures) + return [uploader.process_substream_block(b) for b in uploader.get_substream_blocks()] + + +class _ChunkUploader(object): # pylint: disable=too-many-instance-attributes + + def __init__(self, service, total_size, chunk_size, stream, parallel, encryptor=None, padder=None, **kwargs): + self.service = service + self.total_size = total_size self.chunk_size = chunk_size self.stream = stream self.parallel = parallel + + # Stream management self.stream_start = stream.tell() if parallel else None self.stream_lock = Lock() if parallel else None + + # Progress feedback self.progress_total = 0 self.progress_lock = Lock() if parallel else None - self.validate_content = validate_content - self.lease_access_conditions = access_conditions - self.modified_access_conditions = None - self.append_conditions = append_conditions - self.timeout = timeout + + # Encryption self.encryptor = encryptor self.padder = padder self.response_headers = None @@ -188,8 +155,8 @@ def get_chunk_streams(self): # Buffer until we either reach the end of the stream or get a whole chunk. while True: - if self.blob_size: - read_size = min(self.chunk_size - len(data), self.blob_size - (index + len(data))) + if self.total_size: + read_size = min(self.chunk_size - len(data), self.total_size - (index + len(data))) temp = self.stream.read(read_size) if not isinstance(temp, six.binary_type): raise TypeError('Blob data should be of type bytes.') @@ -239,7 +206,7 @@ def _upload_chunk_with_progress(self, chunk_offset, chunk_data): def get_substream_blocks(self): assert self.chunk_size is not None lock = self.stream_lock - blob_length = self.blob_size + blob_length = self.total_size if blob_length is None: blob_length = get_length(self.stream) @@ -250,9 +217,9 @@ def get_substream_blocks(self): last_block_size = self.chunk_size if blob_length % self.chunk_size == 0 else blob_length % self.chunk_size for i in range(blocks): - yield ('BlockId{}'.format("%05d" % i), - _SubStream(self.stream, i * self.chunk_size, last_block_size if i == blocks - 1 else self.chunk_size, - lock)) + index = i * self.chunk_size + length = last_block_size if i == blocks - 1 else self.chunk_size + yield ('BlockId{}'.format("%05d" % i), SubStream(self.stream, index, length, lock)) def process_substream_block(self, block_data): return self._upload_substream_block_with_progress(block_data[0], block_data[1]) @@ -269,33 +236,27 @@ def set_response_properties(self, resp): self.last_modified = resp.last_modified -class BlockBlobChunkUploader(_BlobChunkUploader): +class BlockBlobChunkUploader(_ChunkUploader): def _upload_chunk(self, chunk_offset, chunk_data): # TODO: This is incorrect, but works with recording. block_id = encode_base64(url_quote(encode_base64('{0:032d}'.format(chunk_offset)))) - self.blob_service.stage_block( + self.service.stage_block( block_id, len(chunk_data), chunk_data, - timeout=self.timeout, - lease_access_conditions=self.lease_access_conditions, - validate_content=self.validate_content, - data_stream_total=self.blob_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options) return block_id def _upload_substream_block(self, block_id, block_stream): try: - self.blob_service.stage_block( + self.service.stage_block( block_id, len(block_stream), block_stream, - validate_content=self.validate_content, - lease_access_conditions=self.lease_access_conditions, - timeout=self.timeout, - data_stream_total=self.blob_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options) finally: @@ -303,7 +264,7 @@ def _upload_substream_block(self, block_id, block_stream): return block_id -class PageBlobChunkUploader(_BlobChunkUploader): # pylint: disable=abstract-method +class PageBlobChunkUploader(_ChunkUploader): # pylint: disable=abstract-method def _is_chunk_empty(self, chunk_data): # read until non-zero byte is encountered @@ -319,26 +280,21 @@ def _upload_chunk(self, chunk_offset, chunk_data): chunk_end = chunk_offset + len(chunk_data) - 1 content_range = 'bytes={0}-{1}'.format(chunk_offset, chunk_end) computed_md5 = None - self.response_headers = self.blob_service.upload_pages( + self.response_headers = self.service.upload_pages( chunk_data, content_length=len(chunk_data), transactional_content_md5=computed_md5, - timeout=self.timeout, range=content_range, - lease_access_conditions=self.lease_access_conditions, - modified_access_conditions=self.modified_access_conditions, - validate_content=self.validate_content, cls=return_response_headers, - data_stream_total=self.blob_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options) - if not self.parallel: - self.modified_access_conditions = ModifiedAccessConditions( - if_match=self.response_headers['etag']) + if not self.parallel and self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = self.response_headers['etag'] -class AppendBlobChunkUploader(_BlobChunkUploader): # pylint: disable=abstract-method +class AppendBlobChunkUploader(_ChunkUploader): # pylint: disable=abstract-method def __init__(self, *args, **kwargs): super(AppendBlobChunkUploader, self).__init__(*args, **kwargs) @@ -346,126 +302,45 @@ def __init__(self, *args, **kwargs): def _upload_chunk(self, chunk_offset, chunk_data): if self.current_length is None: - self.response_headers = self.blob_service.append_block( + self.response_headers = self.service.append_block( chunk_data, content_length=len(chunk_data), - timeout=self.timeout, - lease_access_conditions=self.lease_access_conditions, - modified_access_conditions=self.modified_access_conditions, - validate_content=self.validate_content, - append_position_access_conditions=self.append_conditions, cls=return_response_headers, - data_stream_total=self.blob_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options ) self.current_length = int(self.response_headers['blob_append_offset']) else: - self.append_conditions.append_position = self.current_length + chunk_offset - self.response_headers = self.blob_service.append_block( + self.request_options['append_position_access_conditions'].append_position = \ + self.current_length + chunk_offset + self.response_headers = self.service.append_block( chunk_data, content_length=len(chunk_data), - timeout=self.timeout, - lease_access_conditions=self.lease_access_conditions, - modified_access_conditions=self.modified_access_conditions, - validate_content=self.validate_content, - append_position_access_conditions=self.append_conditions, cls=return_response_headers, - data_stream_total=self.blob_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options ) -class FileChunkUploader(object): # pylint: disable=too-many-instance-attributes - - def __init__(self, file_service, file_size, chunk_size, stream, parallel, - validate_content, timeout, **kwargs): - self.file_service = file_service - self.file_size = file_size - self.chunk_size = chunk_size - self.stream = stream - self.parallel = parallel - self.stream_start = stream.tell() if parallel else None - self.stream_lock = Lock() if parallel else None - self.progress_total = 0 - self.progress_lock = Lock() if parallel else None - self.validate_content = validate_content - self.timeout = timeout - self.request_options = kwargs - - def get_chunk_offsets(self): - index = 0 - if self.file_size is None: - # we don't know the size of the stream, so we have no - # choice but to seek - while True: - data = self._read_from_stream(index, 1) - if not data: - break - yield index - index += self.chunk_size - else: - while index < self.file_size: - yield index - index += self.chunk_size - - def process_chunk(self, chunk_offset): - size = self.chunk_size - if self.file_size is not None: - size = min(size, self.file_size - chunk_offset) - chunk_data = self._read_from_stream(chunk_offset, size) - return self._upload_chunk_with_progress(chunk_offset, chunk_data) - - def process_all_unknown_size(self): - assert self.stream_lock is None - range_ids = [] - index = 0 - while True: - data = self._read_from_stream(None, self.chunk_size) - if data: - index += len(data) - range_id = self._upload_chunk_with_progress(index, data) - range_ids.append(range_id) - else: - break - - return range_ids - - def _read_from_stream(self, offset, count): - if self.stream_lock is not None: - with self.stream_lock: - self.stream.seek(self.stream_start + offset) - data = self.stream.read(count) - else: - data = self.stream.read(count) - return data +class FileChunkUploader(_ChunkUploader): # pylint: disable=abstract-method - def _update_progress(self, length): - if self.progress_lock is not None: - with self.progress_lock: - self.progress_total += length - else: - self.progress_total += length - - def _upload_chunk_with_progress(self, chunk_start, chunk_data): - chunk_end = chunk_start + len(chunk_data) - 1 - self.file_service.upload_range( + def _upload_chunk(self, chunk_offset, chunk_data): + chunk_end = chunk_offset + len(chunk_data) - 1 + self.service.upload_range( chunk_data, - chunk_start, + chunk_offset, chunk_end, - validate_content=self.validate_content, - timeout=self.timeout, - data_stream_total=self.file_size, + data_stream_total=self.total_size, upload_stream_current=self.progress_total, **self.request_options ) - range_id = 'bytes={0}-{1}'.format(chunk_start, chunk_end) - self._update_progress(len(chunk_data)) - return range_id + return 'bytes={0}-{1}'.format(chunk_offset, chunk_end) + +class SubStream(IOBase): -class _SubStream(IOBase): def __init__(self, wrapped_stream, stream_begin_index, length, lockObj): # Python 2.7: file-like objects created with open() typically support seek(), but are not # derivations of io.IOBase and thus do not implement seekable(). @@ -489,7 +364,7 @@ def __init__(self, wrapped_stream, stream_begin_index, length, lockObj): else _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE self._current_buffer_start = 0 self._current_buffer_size = 0 - super(_SubStream, self).__init__() + super(SubStream, self).__init__() def __len__(self): return self._length diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads_async.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads_async.py new file mode 100644 index 000000000000..984a6bf6588b --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/_shared/uploads_async.py @@ -0,0 +1,342 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +# pylint: disable=no-self-use + +import asyncio +from asyncio import Lock +from itertools import islice + +from math import ceil + +import six + +from . import encode_base64, url_quote +from .request_handlers import get_length +from .response_handlers import return_response_headers +from .encryption import get_blob_encryptor_and_padder +from .uploads import SubStream, IterStreamer # pylint: disable=unused-import + + +_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024 +_ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.' + + +async def _parallel_uploads(uploader, pending, running): + range_ids = [] + while True: + # Wait for some download to finish before adding a new one + done, running = await asyncio.wait(running, return_when=asyncio.FIRST_COMPLETED) + range_ids.extend([chunk.result() for chunk in done]) + try: + next_chunk = next(pending) + except StopIteration: + break + else: + running.add(asyncio.ensure_future(uploader.process_chunk(next_chunk))) + + # Wait for the remaining uploads to finish + if running: + done, _running = await asyncio.wait(running) + range_ids.extend([chunk.result() for chunk in done]) + return range_ids + + +async def upload_data_chunks( + service=None, + uploader_class=None, + total_size=None, + chunk_size=None, + max_connections=None, + stream=None, + encryption_options=None, + **kwargs): + + if encryption_options: + encryptor, padder = get_blob_encryptor_and_padder( + encryption_options.get('key'), + encryption_options.get('vector'), + uploader_class is not PageBlobChunkUploader) + kwargs['encryptor'] = encryptor + kwargs['padder'] = padder + + parallel = max_connections > 1 + if parallel and 'modified_access_conditions' in kwargs: + # Access conditions do not work with parallelism + kwargs['modified_access_conditions'] = None + + uploader = uploader_class( + service=service, + total_size=total_size, + chunk_size=chunk_size, + stream=stream, + parallel=parallel, + **kwargs) + + if parallel: + upload_tasks = uploader.get_chunk_streams() + running_futures = [ + asyncio.ensure_future(uploader.process_chunk(u)) + for u in islice(upload_tasks, 0, max_connections) + ] + range_ids = await _parallel_uploads(uploader, upload_tasks, running_futures) + else: + range_ids = [] + for chunk in uploader.get_chunk_streams(): + range_ids.append(await uploader.process_chunk(chunk)) + + if any(range_ids): + return range_ids + return uploader.response_headers + + +async def upload_substream_blocks( + service=None, + uploader_class=None, + total_size=None, + chunk_size=None, + max_connections=None, + stream=None, + **kwargs): + parallel = max_connections > 1 + if parallel and 'modified_access_conditions' in kwargs: + # Access conditions do not work with parallelism + kwargs['modified_access_conditions'] = None + uploader = uploader_class( + service=service, + total_size=total_size, + chunk_size=chunk_size, + stream=stream, + parallel=parallel, + **kwargs) + + if parallel: + upload_tasks = uploader.get_substream_blocks() + running_futures = [ + asyncio.ensure_future(uploader.process_substream_block(u)) + for u in islice(upload_tasks, 0, max_connections) + ] + return await _parallel_uploads(uploader, upload_tasks, running_futures) + blocks = [] + for block in uploader.get_substream_blocks(): + blocks.append(await uploader.process_substream_block(block)) + return blocks + + +class _ChunkUploader(object): # pylint: disable=too-many-instance-attributes + + def __init__(self, service, total_size, chunk_size, stream, parallel, encryptor=None, padder=None, **kwargs): + self.service = service + self.total_size = total_size + self.chunk_size = chunk_size + self.stream = stream + self.parallel = parallel + + # Stream management + self.stream_start = stream.tell() if parallel else None + self.stream_lock = Lock() if parallel else None + + # Progress feedback + self.progress_total = 0 + self.progress_lock = Lock() if parallel else None + + # Encryption + self.encryptor = encryptor + self.padder = padder + self.response_headers = None + self.etag = None + self.last_modified = None + self.request_options = kwargs + + def get_chunk_streams(self): + index = 0 + while True: + data = b'' + read_size = self.chunk_size + + # Buffer until we either reach the end of the stream or get a whole chunk. + while True: + if self.total_size: + read_size = min(self.chunk_size - len(data), self.total_size - (index + len(data))) + temp = self.stream.read(read_size) + if not isinstance(temp, six.binary_type): + raise TypeError('Blob data should be of type bytes.') + data += temp or b"" + + # We have read an empty string and so are at the end + # of the buffer or we have read a full chunk. + if temp == b'' or len(data) == self.chunk_size: + break + + if len(data) == self.chunk_size: + if self.padder: + data = self.padder.update(data) + if self.encryptor: + data = self.encryptor.update(data) + yield index, data + else: + if self.padder: + data = self.padder.update(data) + self.padder.finalize() + if self.encryptor: + data = self.encryptor.update(data) + self.encryptor.finalize() + if data: + yield index, data + break + index += len(data) + + async def process_chunk(self, chunk_data): + chunk_bytes = chunk_data[1] + chunk_offset = chunk_data[0] + return await self._upload_chunk_with_progress(chunk_offset, chunk_bytes) + + async def _update_progress(self, length): + if self.progress_lock is not None: + async with self.progress_lock: + self.progress_total += length + else: + self.progress_total += length + + async def _upload_chunk(self, chunk_offset, chunk_data): + raise NotImplementedError("Must be implemented by child class.") + + async def _upload_chunk_with_progress(self, chunk_offset, chunk_data): + range_id = await self._upload_chunk(chunk_offset, chunk_data) + await self._update_progress(len(chunk_data)) + return range_id + + def get_substream_blocks(self): + assert self.chunk_size is not None + lock = self.stream_lock + blob_length = self.total_size + + if blob_length is None: + blob_length = get_length(self.stream) + if blob_length is None: + raise ValueError("Unable to determine content length of upload data.") + + blocks = int(ceil(blob_length / (self.chunk_size * 1.0))) + last_block_size = self.chunk_size if blob_length % self.chunk_size == 0 else blob_length % self.chunk_size + + for i in range(blocks): + index = i * self.chunk_size + length = last_block_size if i == blocks - 1 else self.chunk_size + yield ('BlockId{}'.format("%05d" % i), SubStream(self.stream, index, length, lock)) + + async def process_substream_block(self, block_data): + return await self._upload_substream_block_with_progress(block_data[0], block_data[1]) + + async def _upload_substream_block(self, block_id, block_stream): + raise NotImplementedError("Must be implemented by child class.") + + async def _upload_substream_block_with_progress(self, block_id, block_stream): + range_id = self._upload_substream_block(block_id, block_stream) + await self._update_progress(len(block_stream)) + return range_id + + def set_response_properties(self, resp): + self.etag = resp.etag + self.last_modified = resp.last_modified + + +class BlockBlobChunkUploader(_ChunkUploader): + + async def _upload_chunk(self, chunk_offset, chunk_data): + # TODO: This is incorrect, but works with recording. + block_id = encode_base64(url_quote(encode_base64('{0:032d}'.format(chunk_offset)))) + await self.service.stage_block( + block_id, + len(chunk_data), + chunk_data, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options) + return block_id + + async def _upload_substream_block(self, block_id, block_stream): + try: + await self.service.stage_block( + block_id, + len(block_stream), + block_stream, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options) + finally: + block_stream.close() + return block_id + + +class PageBlobChunkUploader(_ChunkUploader): # pylint: disable=abstract-method + + def _is_chunk_empty(self, chunk_data): + # read until non-zero byte is encountered + # if reached the end without returning, then chunk_data is all 0's + for each_byte in chunk_data: + if each_byte not in [0, b'\x00']: + return False + return True + + async def _upload_chunk(self, chunk_offset, chunk_data): + # avoid uploading the empty pages + if not self._is_chunk_empty(chunk_data): + chunk_end = chunk_offset + len(chunk_data) - 1 + content_range = 'bytes={0}-{1}'.format(chunk_offset, chunk_end) + computed_md5 = None + self.response_headers = await self.service.upload_pages( + chunk_data, + content_length=len(chunk_data), + transactional_content_md5=computed_md5, + range=content_range, + cls=return_response_headers, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options) + + if not self.parallel and self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = self.response_headers['etag'] + + +class AppendBlobChunkUploader(_ChunkUploader): # pylint: disable=abstract-method + + def __init__(self, *args, **kwargs): + super(AppendBlobChunkUploader, self).__init__(*args, **kwargs) + self.current_length = None + + async def _upload_chunk(self, chunk_offset, chunk_data): + if self.current_length is None: + self.response_headers = await self.service.append_block( + chunk_data, + content_length=len(chunk_data), + cls=return_response_headers, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options) + self.current_length = int(self.response_headers['blob_append_offset']) + else: + self.request_options['append_position_access_conditions'].append_position = \ + self.current_length + chunk_offset + self.response_headers = await self.service.append_block( + chunk_data, + content_length=len(chunk_data), + cls=return_response_headers, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options) + + +class FileChunkUploader(_ChunkUploader): # pylint: disable=abstract-method + + async def _upload_chunk(self, chunk_offset, chunk_data): + chunk_end = chunk_offset + len(chunk_data) - 1 + await self.service.upload_range( + chunk_data, + chunk_offset, + chunk_end, + data_stream_total=self.total_size, + upload_stream_current=self.progress_total, + **self.request_options + ) + range_id = 'bytes={0}-{1}'.format(chunk_offset, chunk_end) + return range_id diff --git a/sdk/storage/azure-storage-file/azure/storage/file/_shared/utils.py b/sdk/storage/azure-storage-file/azure/storage/file/_shared/utils.py deleted file mode 100644 index e5d0f3a88d67..000000000000 --- a/sdk/storage/azure-storage-file/azure/storage/file/_shared/utils.py +++ /dev/null @@ -1,614 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - -from typing import ( # pylint: disable=unused-import - Union, Optional, Any, Iterable, Dict, List, Type, Tuple, - TYPE_CHECKING -) -import base64 -import hashlib -import hmac -import logging -from os import fstat -from io import (SEEK_END, SEEK_SET, UnsupportedOperation) - -try: - from urllib.parse import quote, unquote, parse_qs -except ImportError: - from urlparse import parse_qs # type: ignore - from urllib2 import quote, unquote # type: ignore - -import six -import isodate - -from azure.core import Configuration -from azure.core.exceptions import raise_with_traceback -from azure.core.pipeline import Pipeline -from azure.core.pipeline.transport import RequestsTransport -from azure.core.pipeline.policies import ( - RedirectPolicy, - ContentDecodePolicy, - BearerTokenCredentialPolicy, - ProxyPolicy) -from azure.core.exceptions import ( - HttpResponseError, - ResourceNotFoundError, - ResourceModifiedError, - ResourceExistsError, - ClientAuthenticationError, - DecodeError) - -from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT -from .models import LocationMode, StorageErrorCode -from .authentication import SharedKeyCredentialPolicy -from .policies import ( - StorageDataSettings, - StorageHeadersPolicy, - StorageUserAgentPolicy, - StorageContentValidation, - StorageRequestHook, - StorageResponseHook, - StorageLoggingPolicy, - StorageHosts, - QueueMessagePolicy, - ExponentialRetry) - - -if TYPE_CHECKING: - from datetime import datetime - from azure.core.pipeline.transport import HttpTransport - from azure.core.pipeline.policies import HTTPPolicy - from azure.core.exceptions import AzureError - - -_LOGGER = logging.getLogger(__name__) - - -class _QueryStringConstants(object): - SIGNED_SIGNATURE = 'sig' - SIGNED_PERMISSION = 'sp' - SIGNED_START = 'st' - SIGNED_EXPIRY = 'se' - SIGNED_RESOURCE = 'sr' - SIGNED_IDENTIFIER = 'si' - SIGNED_IP = 'sip' - SIGNED_PROTOCOL = 'spr' - SIGNED_VERSION = 'sv' - SIGNED_CACHE_CONTROL = 'rscc' - SIGNED_CONTENT_DISPOSITION = 'rscd' - SIGNED_CONTENT_ENCODING = 'rsce' - SIGNED_CONTENT_LANGUAGE = 'rscl' - SIGNED_CONTENT_TYPE = 'rsct' - START_PK = 'spk' - START_RK = 'srk' - END_PK = 'epk' - END_RK = 'erk' - SIGNED_RESOURCE_TYPES = 'srt' - SIGNED_SERVICES = 'ss' - - @staticmethod - def to_list(): - return [ - _QueryStringConstants.SIGNED_SIGNATURE, - _QueryStringConstants.SIGNED_PERMISSION, - _QueryStringConstants.SIGNED_START, - _QueryStringConstants.SIGNED_EXPIRY, - _QueryStringConstants.SIGNED_RESOURCE, - _QueryStringConstants.SIGNED_IDENTIFIER, - _QueryStringConstants.SIGNED_IP, - _QueryStringConstants.SIGNED_PROTOCOL, - _QueryStringConstants.SIGNED_VERSION, - _QueryStringConstants.SIGNED_CACHE_CONTROL, - _QueryStringConstants.SIGNED_CONTENT_DISPOSITION, - _QueryStringConstants.SIGNED_CONTENT_ENCODING, - _QueryStringConstants.SIGNED_CONTENT_LANGUAGE, - _QueryStringConstants.SIGNED_CONTENT_TYPE, - _QueryStringConstants.START_PK, - _QueryStringConstants.START_RK, - _QueryStringConstants.END_PK, - _QueryStringConstants.END_RK, - _QueryStringConstants.SIGNED_RESOURCE_TYPES, - _QueryStringConstants.SIGNED_SERVICES, - ] - - -class StorageAccountHostsMixin(object): - - def __init__( - self, parsed_url, # type: Any - service, # type: str - credential=None, # type: Optional[Any] - **kwargs # type: Any - ): - # type: (...) -> None - self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) - self._hosts = kwargs.get('_hosts') - self.scheme = parsed_url.scheme - - if service not in ['blob', 'queue', 'file']: - raise ValueError("Invalid service: {}".format(service)) - account = parsed_url.netloc.split(".{}.core.".format(service)) - secondary_hostname = None - self.credential = format_shared_key_credential(account, credential) - if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): - raise ValueError("Token credential is only supported with HTTPS.") - if hasattr(self.credential, 'account_name'): - secondary_hostname = "{}-secondary.{}.{}".format( - self.credential.account_name, service, SERVICE_HOST_BASE) - - if not self._hosts: - if len(account) > 1: - secondary_hostname = parsed_url.netloc.replace( - account[0], - account[0] + '-secondary') - if kwargs.get('secondary_hostname'): - secondary_hostname = kwargs['secondary_hostname'] - self._hosts = { - LocationMode.PRIMARY: parsed_url.netloc, - LocationMode.SECONDARY: secondary_hostname} - - self.require_encryption = kwargs.get('require_encryption', False) - self.key_encryption_key = kwargs.get('key_encryption_key') - self.key_resolver_function = kwargs.get('key_resolver_function') - - self._config, self._pipeline = create_pipeline(self.credential, hosts=self._hosts, **kwargs) - - def __enter__(self): - self._client.__enter__() - return self - - def __exit__(self, *args): - self._client.__exit__(*args) - - @property - def url(self): - return self._format_url(self._hosts[self._location_mode]) - - @property - def primary_endpoint(self): - return self._format_url(self._hosts[LocationMode.PRIMARY]) - - @property - def primary_hostname(self): - return self._hosts[LocationMode.PRIMARY] - - @property - def secondary_endpoint(self): - if not self._hosts[LocationMode.SECONDARY]: - raise ValueError("No secondary host configured.") - return self._format_url(self._hosts[LocationMode.SECONDARY]) - - @property - def secondary_hostname(self): - return self._hosts[LocationMode.SECONDARY] - - @property - def location_mode(self): - return self._location_mode - - @location_mode.setter - def location_mode(self, value): - if self._hosts.get(value): - self._location_mode = value - self._client._config.url = self.url # pylint: disable=protected-access - else: - raise ValueError("No host URL for location mode: {}".format(value)) - - def _format_query_string(self, sas_token, credential, snapshot=None, share_snapshot=None): - query_str = "?" - if snapshot: - query_str += 'snapshot={}&'.format(self.snapshot) - if share_snapshot: - query_str += 'sharesnapshot={}&'.format(self.snapshot) - if sas_token and not credential: - query_str += sas_token - elif is_credential_sastoken(credential): - query_str += credential.lstrip('?') - credential = None - return query_str.rstrip('?&'), credential - - -def format_shared_key_credential(account, credential): - if isinstance(credential, six.string_types): - if len(account) < 2: - raise ValueError("Unable to determine account name for shared key credential.") - credential = { - 'account_name': account[0], - 'account_key': credential - } - if isinstance(credential, dict): - if 'account_name' not in credential: - raise ValueError("Shared key credential missing 'account_name") - if 'account_key' not in credential: - raise ValueError("Shared key credential missing 'account_key") - return SharedKeyCredentialPolicy(**credential) - return credential - - -service_connection_params = { - 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, - 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, - 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, -} - - -def parse_connection_str(conn_str, credential, service): - conn_str = conn_str.rstrip(';') - conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension - endpoints = service_connection_params[service] - primary = None - secondary = None - if not credential: - try: - credential = { - 'account_name': conn_settings['AccountName'], - 'account_key': conn_settings['AccountKey'] - } - except KeyError: - credential = conn_settings.get('SharedAccessSignature') - if endpoints['primary'] in conn_settings: - primary = conn_settings[endpoints['primary']] - if endpoints['secondary'] in conn_settings: - secondary = conn_settings[endpoints['secondary']] - else: - if endpoints['secondary'] in conn_settings: - raise ValueError("Connection string specifies only secondary endpoint.") - try: - primary = "{}://{}.{}.{}".format( - conn_settings['DefaultEndpointsProtocol'], - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - secondary = "{}-secondary.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - except KeyError: - pass - - if not primary: - try: - primary = "https://{}.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) - ) - except KeyError: - raise ValueError("Connection string missing required connection details.") - return primary, secondary, credential - - -def url_quote(url): - return quote(url) - - -def url_unquote(url): - return unquote(url) - - -def encode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') - - -def decode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - decoded = base64.b64decode(data) - return decoded.decode('utf-8') - - -def _decode_base64_to_bytes(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - return base64.b64decode(data) - - -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, six.text_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, six.text_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = encode_base64(digest) - return encoded_digest - - -def serialize_iso(attr): - """Serialize Datetime object into ISO-8601 formatted string. - - :param Datetime attr: Object to be serialized. - :rtype: str - :raises: ValueError if format invalid. - """ - if not attr: - return None - if isinstance(attr, str): - attr = isodate.parse_datetime(attr) - try: - utc = attr.utctimetuple() - if utc.tm_year > 9999 or utc.tm_year < 1: - raise OverflowError("Hit max or min date") - - date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( - utc.tm_year, utc.tm_mon, utc.tm_mday, - utc.tm_hour, utc.tm_min, utc.tm_sec) - return date + 'Z' - except (ValueError, OverflowError) as err: - msg = "Unable to serialize datetime object." - raise_with_traceback(ValueError, msg, err) - except AttributeError as err: - msg = "ISO-8601 object must be valid Datetime object." - raise_with_traceback(TypeError, msg, err) - - -def get_length(data): - length = None - # Check if object implements the __len__ method, covers most input cases such as bytearray. - try: - length = len(data) - except: # pylint: disable=bare-except - pass - - if not length: - # Check if the stream is a file-like stream object. - # If so, calculate the size using the file descriptor. - try: - fileno = data.fileno() - except (AttributeError, UnsupportedOperation): - pass - else: - return fstat(fileno).st_size - - # If the stream is seekable and tell() is implemented, calculate the stream size. - try: - current_position = data.tell() - data.seek(0, SEEK_END) - length = data.tell() - current_position - data.seek(current_position, SEEK_SET) - except (AttributeError, UnsupportedOperation): - pass - - return length - - -def read_length(data): - try: - if hasattr(data, 'read'): - read_data = b'' - for chunk in iter(lambda: data.read(4096), b""): - read_data += chunk - return len(read_data), read_data - if hasattr(data, '__iter__'): - read_data = b'' - for chunk in data: - read_data += chunk - return len(read_data), read_data - except: # pylint: disable=bare-except - pass - raise ValueError("Unable to calculate content length, please specify.") - - -def parse_length_from_content_range(content_range): - ''' - Parses the blob length from the content range header: bytes 1-3/65537 - ''' - if content_range is None: - return None - - # First, split in space and take the second half: '1-3/65537' - # Next, split on slash and take the second half: '65537' - # Finally, convert to an int: 65537 - return int(content_range.split(' ', 1)[1].split('/', 1)[1]) - - -def validate_and_format_range_headers( - start_range, end_range, start_range_required=True, - end_range_required=True, check_content_md5=False, align_to_page=False): - # If end range is provided, start range must be provided - if (start_range_required or end_range is not None) and start_range is None: - raise ValueError("start_range value cannot be None.") - if end_range_required and end_range is None: - raise ValueError("end_range value cannot be None.") - - # Page ranges must be 512 aligned - if align_to_page: - if start_range is not None and start_range % 512 != 0: - raise ValueError("Invalid page blob start_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(start_range)) - if end_range is not None and end_range % 512 != 511: - raise ValueError("Invalid page blob end_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(end_range)) - - # Format based on whether end_range is present - range_header = None - if end_range is not None: - range_header = 'bytes={0}-{1}'.format(start_range, end_range) - elif start_range is not None: - range_header = "bytes={0}-".format(start_range) - - # Content MD5 can only be provided for a complete range less than 4MB in size - range_validation = None - if check_content_md5: - if start_range is None or end_range is None: - raise ValueError("Both start and end range requied for MD5 content validation.") - if end_range - start_range > 4 * 1024 * 1024: - raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") - range_validation = 'true' - - return range_header, range_validation - - -def normalize_headers(headers): - normalized = {} - for key, value in headers.items(): - if key.startswith('x-ms-'): - key = key[5:] - normalized[key.lower().replace('-', '_')] = value - return normalized - - -def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers) - - -def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers), deserialized - - -def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return response.location_mode, deserialized - - -def create_configuration(**kwargs): - # type: (**Any) -> Configuration - if 'connection_timeout' not in kwargs: - kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT - config = Configuration(**kwargs) - config.headers_policy = StorageHeadersPolicy(**kwargs) - config.user_agent_policy = StorageUserAgentPolicy(**kwargs) - config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) - config.redirect_policy = RedirectPolicy(**kwargs) - config.logging_policy = StorageLoggingPolicy(**kwargs) - config.proxy_policy = ProxyPolicy(**kwargs) - config.data_settings = StorageDataSettings(**kwargs) - return config - - -def create_pipeline(credential, **kwargs): - # type: (Any, **Any) -> Tuple[Configuration, Pipeline] - credential_policy = None - if hasattr(credential, 'get_token'): - credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) - elif isinstance(credential, SharedKeyCredentialPolicy): - credential_policy = credential - elif credential is not None: - raise TypeError("Unsupported credential: {}".format(credential)) - - config = kwargs.get('_configuration') or create_configuration(**kwargs) - if kwargs.get('_pipeline'): - return config, kwargs['_pipeline'] - transport = kwargs.get('transport') # type: HttpTransport - if not transport: - transport = RequestsTransport(config) - policies = [ - QueueMessagePolicy(), - config.headers_policy, - config.user_agent_policy, - StorageContentValidation(), - StorageRequestHook(**kwargs), - credential_policy, - ContentDecodePolicy(), - config.redirect_policy, - StorageHosts(**kwargs), - config.retry_policy, - config.logging_policy, - StorageResponseHook(**kwargs), - ] - return config, Pipeline(transport, policies=policies) - - -def parse_query(query_str): - sas_values = _QueryStringConstants.to_list() - parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} - sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] - sas_token = None - if sas_params: - sas_token = '&'.join(sas_params) - - snapshot = parsed_query.get('snapshot') or parsed_query.get('sharesnapshot') - return snapshot, sas_token - - -def is_credential_sastoken(credential): - if not credential or not isinstance(credential, six.string_types): - return False - - sas_values = _QueryStringConstants.to_list() - parsed_query = parse_qs(credential.lstrip('?')) - if parsed_query and all([k in sas_values for k in parsed_query.keys()]): - return True - return False - - -def add_metadata_headers(metadata=None): - # type: (Optional[Dict[str, str]]) -> Dict[str, str] - headers = {} - if metadata: - for key, value in metadata.items(): - headers['x-ms-meta-{}'.format(key)] = value - return headers - - -def process_storage_error(storage_error): - raise_error = HttpResponseError - error_code = storage_error.response.headers.get('x-ms-error-code') - error_message = storage_error.message - additional_data = {} - try: - error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) - if error_body: - for info in error_body.iter(): - if info.tag.lower() == 'code': - error_code = info.text - elif info.tag.lower() == 'message': - error_message = info.text - else: - additional_data[info.tag] = info.text - except DecodeError: - pass - - try: - if error_code: - error_code = StorageErrorCode(error_code) - if error_code in [StorageErrorCode.condition_not_met, - StorageErrorCode.blob_overwritten]: - raise_error = ResourceModifiedError - if error_code in [StorageErrorCode.invalid_authentication_info, - StorageErrorCode.authentication_failed]: - raise_error = ClientAuthenticationError - if error_code in [StorageErrorCode.resource_not_found, - StorageErrorCode.blob_not_found, - StorageErrorCode.queue_not_found, - StorageErrorCode.container_not_found, - StorageErrorCode.parent_not_found, - StorageErrorCode.share_not_found]: - raise_error = ResourceNotFoundError - if error_code in [StorageErrorCode.account_already_exists, - StorageErrorCode.account_being_created, - StorageErrorCode.resource_already_exists, - StorageErrorCode.resource_type_mismatch, - StorageErrorCode.blob_already_exists, - StorageErrorCode.queue_already_exists, - StorageErrorCode.container_already_exists, - StorageErrorCode.container_being_deleted, - StorageErrorCode.queue_being_deleted, - StorageErrorCode.share_already_exists, - StorageErrorCode.share_being_deleted]: - raise_error = ResourceExistsError - except ValueError: - # Got an unknown error code - pass - - try: - error_message += "\nErrorCode:{}".format(error_code.value) - except AttributeError: - error_message += "\nErrorCode:{}".format(error_code) - for name, info in additional_data.items(): - error_message += "\n{}:{}".format(name, info) - - error = raise_error(message=error_message, response=storage_error.response) - error.error_code = error_code - error.additional_info = additional_data - raise error diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/__init__.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/__init__.py new file mode 100644 index 000000000000..11b419d0fbe9 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/__init__.py @@ -0,0 +1,61 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from .file_client_async import FileClient +from .directory_client_async import DirectoryClient +from .share_client_async import ShareClient +from .file_service_client_async import FileServiceClient +from .._shared.policies_async import ExponentialRetry, LinearRetry, NoRetry +from .._shared.models import ( + LocationMode, + ResourceTypes, + AccountPermissions, + StorageErrorCode) +from ..models import ( + Handle, + ShareProperties, + DirectoryProperties, + FileProperties, + Metrics, + RetentionPolicy, + CorsRule, + AccessPolicy, + FilePermissions, + SharePermissions, + ContentSettings) +from .models import ( + HandlesPaged, + SharePropertiesPaged, + DirectoryPropertiesPaged) + + +__all__ = [ + 'FileClient', + 'DirectoryClient', + 'ShareClient', + 'FileServiceClient', + 'ExponentialRetry', + 'LinearRetry', + 'NoRetry', + 'LocationMode', + 'ResourceTypes', + 'AccountPermissions', + 'StorageErrorCode', + 'Metrics', + 'RetentionPolicy', + 'CorsRule', + 'Handle', + 'HandlesPaged', + 'AccessPolicy', + 'FilePermissions', + 'SharePermissions', + 'ShareProperties', + 'SharePropertiesPaged', + 'DirectoryProperties', + 'DirectoryPropertiesPaged', + 'FileProperties', + 'ContentSettings' +] diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/_polling_async.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/_polling_async.py new file mode 100644 index 000000000000..d4d631b06438 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/_polling_async.py @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import asyncio +import logging +from typing import Any, Callable +from azure.core.polling import AsyncPollingMethod + +from .._shared.response_handlers import process_storage_error +from .._generated.models import StorageErrorException + + +logger = logging.getLogger(__name__) + + +class CloseHandlesAsync(AsyncPollingMethod): + + def __init__(self, interval): + self._command = None + self._status = None + self._exception = None + self.handles_closed = 0 + self.polling_interval = interval + + async def _update_status(self): + try: + status = await self._command() # pylint: disable=protected-access + except StorageErrorException as error: + process_storage_error(error) + self._status = status.get('marker') + self.handles_closed += status['number_of_handles_closed'] + + def initialize(self, command, initial_status, _): # pylint: disable=arguments-differ + # type: (Any, Any, Callable) -> None + self._command = command + self._status = initial_status['marker'] + self.handles_closed = initial_status['number_of_handles_closed'] + + async def run(self): + # type: () -> None + try: + while not self.finished(): + await self._update_status() + await asyncio.sleep(self.polling_interval) + except Exception as e: + logger.warning(str(e)) + raise + + def status(self): + return self.handles_closed + + def finished(self): + # type: () -> bool + """Is this polling finished? + :rtype: bool + """ + return self._status is None + + def resource(self): + # type: () -> Any + return self.handles_closed diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/directory_client_async.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/directory_client_async.py new file mode 100644 index 000000000000..3d894da41a00 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/directory_client_async.py @@ -0,0 +1,490 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import functools +from typing import ( # pylint: disable=unused-import + Optional, Union, Any, Dict, TYPE_CHECKING +) + +from azure.core.polling import async_poller + +from .._generated.aio import AzureFileStorage +from .._generated.version import VERSION +from .._generated.models import StorageErrorException +from .._shared.base_client_async import AsyncStorageAccountHostsMixin +from .._shared.policies_async import ExponentialRetry +from .._shared.request_handlers import add_metadata_headers +from .._shared.response_handlers import return_response_headers, process_storage_error +from .._deserialize import deserialize_directory_properties +from ..directory_client import DirectoryClient as DirectoryClientBase +from ._polling_async import CloseHandlesAsync +from .file_client_async import FileClient +from .models import DirectoryPropertiesPaged, HandlesPaged + +if TYPE_CHECKING: + from ..models import SharePermissions, ShareProperties, DirectoryProperties, ContentSettings + from .._generated.models import HandleItem + + +class DirectoryClient(AsyncStorageAccountHostsMixin, DirectoryClientBase): + """A client to interact with a specific directory, although it may not yet exist. + + For operations relating to a specific subdirectory or file in this share, the clients for those + entities can also be retrieved using the `get_subdirectory_client` and `get_file_client` functions.s + + :ivar str url: + The full endpoint URL to the Directory, including SAS token if used. This could be + either the primary endpoint, or the secondard endpoint depending on the current `location_mode`. + :ivar str primary_endpoint: + The full primary endpoint URL. + :ivar str primary_hostname: + The hostname of the primary endpoint. + :ivar str secondary_endpoint: + The full secondard endpoint URL if configured. If not available + a ValueError will be raised. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str secondary_hostname: + The hostname of the secondary endpoint. If not available this + will be None. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str location_mode: + The location mode that the client is currently using. By default + this will be "primary". Options include "primary" and "secondary". + :param str directory_url: + The full URI to the directory. This can also be a URL to the storage account + or share, in which case the directory and/or share must also be specified. + :param share: The share for the directory. If specified, this value will override + a share value specified in the directory URL. + :type share: str or ~azure.storage.file.models.ShareProperties + :param str directory_path: + The directory path for the directory with which to interact. + If specified, this value will override a directory value specified in the directory URL. + :param str snapshot: + An optional share snapshot on which to operate. + :param credential: + The credential with which to authenticate. This is optional if the + account URL already has a SAS token. The value can be a SAS token string or an account + shared access key. + """ + def __init__( # type: ignore + self, directory_url, # type: str + share=None, # type: Optional[Union[str, ShareProperties]] + directory_path=None, # type: Optional[str] + snapshot=None, # type: Optional[Union[str, Dict[str, Any]]] + credential=None, # type: Optional[Any] + loop=None, # type: Any + **kwargs # type: Optional[Any] + ): + # type: (...) -> None + kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + super(DirectoryClient, self).__init__( + directory_url, + share=share, + directory_path=directory_path, + snapshot=snapshot, + credential=credential, + loop=loop, + **kwargs) + self._client = AzureFileStorage(version=VERSION, url=self.url, pipeline=self._pipeline, loop=loop) + self._loop = loop + + def get_file_client(self, file_name, **kwargs): + """Get a client to interact with a specific file. + + The file need not already exist. + + :param file_name: + The name of the file. + :returns: A File Client. + :rtype: ~azure.storage.file.file_client.FileClient + """ + if self.directory_path: + file_name = self.directory_path.rstrip('/') + "/" + file_name + return FileClient( + self.url, file_path=file_name, snapshot=self.snapshot, credential=self.credential, + _hosts=self._hosts, _configuration=self._config, _pipeline=self._pipeline, + _location_mode=self._location_mode, loop=self._loop, **kwargs) + + def get_subdirectory_client(self, directory_name, **kwargs): + """Get a client to interact with a specific subdirectory. + + The subdirectory need not already exist. + + :param str directory_name: + The name of the subdirectory. + :returns: A Directory Client. + :rtype: ~azure.storage.file.directory_client.DirectoryClient + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START get_subdirectory_client] + :end-before: [END get_subdirectory_client] + :language: python + :dedent: 12 + :caption: Gets the subdirectory client. + """ + directory_path = self.directory_path.rstrip('/') + "/" + directory_name + return DirectoryClient( + self.url, directory_path=directory_path, snapshot=self.snapshot, credential=self.credential, + _hosts=self._hosts, _configuration=self._config, _pipeline=self._pipeline, + _location_mode=self._location_mode, loop=self._loop, **kwargs) + + async def create_directory( # type: ignore + self, metadata=None, # type: Optional[Dict[str, str]] + timeout=None, # type: Optional[int] + **kwargs # type: Optional[Any] + ): + # type: (...) -> Dict[str, Any] + """Creates a new directory under the directory referenced by the client. + + :param metadata: + Name-value pairs associated with the directory as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Directory-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START create_directory] + :end-before: [END create_directory] + :language: python + :dedent: 12 + :caption: Creates a directory. + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) # type: ignore + try: + return await self._client.directory.create( # type: ignore + timeout=timeout, + cls=return_response_headers, + headers=headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def delete_directory(self, timeout=None, **kwargs): + # type: (Optional[int], **Any) -> None + """Marks the directory for deletion. The directory is + later deleted during garbage collection. + + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START delete_directory] + :end-before: [END delete_directory] + :language: python + :dedent: 12 + :caption: Deletes a directory. + """ + try: + await self._client.directory.delete(timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + def list_directories_and_files(self, name_starts_with=None, marker=None, timeout=None, **kwargs): + # type: (Optional[str], Optional[str], Optional[int], **Any) -> DirectoryProperties + """Lists all the directories and files under the directory. + + :param str name_starts_with: + Filters the results to return only entities whose names + begin with the specified prefix. + :param str marker: + An opaque continuation token. This value can be retrieved from the + next_marker field of a previous generator object. If specified, + this generator will begin returning results from this point. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: An auto-paging iterable of dict-like DirectoryProperties and FileProperties + :rtype: ~azure.storage.file.models.DirectoryPropertiesPaged + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START lists_directory] + :end-before: [END lists_directory] + :language: python + :dedent: 12 + :caption: List directories and files. + """ + results_per_page = kwargs.pop('results_per_page', None) + command = functools.partial( + self._client.directory.list_files_and_directories_segment, + sharesnapshot=self.snapshot, + timeout=timeout, + **kwargs) + return DirectoryPropertiesPaged( + command, prefix=name_starts_with, results_per_page=results_per_page, marker=marker) + + def list_handles(self, marker=None, recursive=False, timeout=None, **kwargs): + """Lists opened handles on a directory or a file under the directory. + + :param str marker: + An opaque continuation token. This value can be retrieved from the + next_marker field of a previous generator object. If specified, + this generator will begin returning results from this point. + :param bool recursive: + Boolean that specifies if operation should apply to the directory specified by the client, + its files, its subdirectories and their files. Default value is False. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: An auto-paging iterable of HandleItems + :rtype: ~azure.storage.file.models.HandlesPaged + """ + results_per_page = kwargs.pop('results_per_page', None) + command = functools.partial( + self._client.directory.list_handles, + sharesnapshot=self.snapshot, + timeout=timeout, + recursive=recursive, + **kwargs) + return HandlesPaged( + command, results_per_page=results_per_page, marker=marker) + + async def close_handles( + self, handle=None, # type: Union[str, HandleItem] + recursive=False, # type: bool + timeout=None, # type: Optional[int] + **kwargs # type: Any + ): + # type: (...) -> Any + """Close open file handles. + + This operation may not finish with a single call, so a long-running poller + is returned that can be used to wait until the operation is complete. + + :param handle: + Optionally, a specific handle to close. The default value is '*' + which will attempt to close all open handles. + :type handle: str or ~azure.storage.file.models.Handle + :param bool recursive: + Boolean that specifies if operation should apply to the directory specified by the client, + its files, its subdirectories and their files. Default value is False. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: A long-running poller to get operation status. + :rtype: ~azure.core.polling.LROPoller + """ + try: + handle_id = handle.id # type: ignore + except AttributeError: + handle_id = handle or '*' + command = functools.partial( + self._client.directory.force_close_handles, + handle_id, + timeout=timeout, + sharesnapshot=self.snapshot, + recursive=recursive, + cls=return_response_headers, + **kwargs) + try: + start_close = await command() + except StorageErrorException as error: + process_storage_error(error) + + polling_method = CloseHandlesAsync(self._config.copy_polling_interval) + return async_poller( + command, + start_close, + None, + polling_method) + + async def get_directory_properties(self, timeout=None, **kwargs): + # type: (Optional[int], Any) -> DirectoryProperties + """Returns all user-defined metadata and system properties for the + specified directory. The data returned does not include the directory's + list of files. + + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: DirectoryProperties + :rtype: ~azure.storage.file.models.DirectoryProperties + """ + try: + response = await self._client.directory.get_properties( + timeout=timeout, + cls=deserialize_directory_properties, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + return response # type: ignore + + async def set_directory_metadata(self, metadata, timeout=None, **kwargs): # type: ignore + # type: (Dict[str, Any], Optional[int], Any) -> Dict[str, Any] + """Sets the metadata for the directory. + + Each call to this operation replaces all existing metadata + attached to the directory. To remove all metadata from the directory, + call this operation with an empty metadata dict. + + :param metadata: + Name-value pairs associated with the directory as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Directory-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) + try: + return await self._client.directory.set_metadata( # type: ignore + timeout=timeout, + cls=return_response_headers, + headers=headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def create_subdirectory( + self, directory_name, # type: str + metadata=None, #type: Optional[Dict[str, Any]] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> DirectoryClient + """Creates a new subdirectory and returns a client to interact + with the subdirectory. + + :param str directory_name: + The name of the subdirectory. + :param metadata: + Name-value pairs associated with the subdirectory as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: DirectoryClient + :rtype: ~azure.storage.file.directory_client.DirectoryClient + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START create_subdirectory] + :end-before: [END create_subdirectory] + :language: python + :dedent: 12 + :caption: Create a subdirectory. + """ + subdir = self.get_subdirectory_client(directory_name) + await subdir.create_directory(metadata=metadata, timeout=timeout, **kwargs) + return subdir # type: ignore + + async def delete_subdirectory( + self, directory_name, # type: str + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> None + """Deletes a subdirectory. + + :param str directory_name: + The name of the subdirectory. + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START delete_subdirectory] + :end-before: [END delete_subdirectory] + :language: python + :dedent: 12 + :caption: Delete a subdirectory. + """ + subdir = self.get_subdirectory_client(directory_name) + await subdir.delete_directory(timeout=timeout, **kwargs) + + async def upload_file( + self, file_name, # type: str + data, # type: Any + length=None, # type: Optional[int] + metadata=None, # type: Optional[Dict[str, str]] + content_settings=None, # type: Optional[ContentSettings] + validate_content=False, # type: bool + max_connections=1, # type: Optional[int] + timeout=None, # type: Optional[int] + encoding='UTF-8', # type: str + **kwargs # type: Any + ): + # type: (...) -> FileClient + """Creates a new file in the directory and returns a FileClient + to interact with the file. + + :param str file_name: + The name of the file. + :param Any data: + Content of the file. + :param int length: + Length of the file in bytes. Specify its maximum size, up to 1 TiB. + :param metadata: + Name-value pairs associated with the file as metadata. + :type metadata: dict(str, str) + :param ~azure.storage.file.models.ContentSettings content_settings: + ContentSettings object used to set file properties. + :param bool validate_content: + If true, calculates an MD5 hash for each range of the file. The storage + service checks the hash of the content that has arrived with the hash + that was sent. This is primarily valuable for detecting bitflips on + the wire if using http instead of https as https (the default) will + already validate. Note that this MD5 hash is not stored with the + file. + :param int max_connections: + Maximum number of parallel connections to use. + :param int timeout: + The timeout parameter is expressed in seconds. + :param str encoding: + Defaults to UTF-8. + :returns: FileClient + :rtype: ~azure.storage.file.file_client.FileClient + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START upload_file_to_directory] + :end-before: [END upload_file_to_directory] + :language: python + :dedent: 12 + :caption: Upload a file to a directory. + """ + file_client = self.get_file_client(file_name) + await file_client.upload_file( + data, + length=length, + metadata=metadata, + content_settings=content_settings, + validate_content=validate_content, + max_connections=max_connections, + timeout=timeout, + encoding=encoding, + **kwargs) + return file_client # type: ignore + + async def delete_file( + self, file_name, # type: str + timeout=None, # type: Optional[int] + **kwargs # type: Optional[Any] + ): + # type: (...) -> None + """Marks the specified file for deletion. The file is later + deleted during garbage collection. + + :param str file_name: + The name of the file to delete. + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_directory.py + :start-after: [START delete_file_in_directory] + :end-before: [END delete_file_in_directory] + :language: python + :dedent: 12 + :caption: Delete a file in a directory. + """ + file_client = self.get_file_client(file_name) + await file_client.delete_file(timeout, **kwargs) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/file_client_async.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/file_client_async.py new file mode 100644 index 000000000000..13726d9100cf --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/file_client_async.py @@ -0,0 +1,726 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import functools +from io import BytesIO +from typing import ( # pylint: disable=unused-import + Optional, Union, IO, List, Dict, Any, Iterable, + TYPE_CHECKING +) + +import six +from azure.core.polling import async_poller + +from .._generated.aio import AzureFileStorage +from .._generated.version import VERSION +from .._generated.models import StorageErrorException, FileHTTPHeaders +from .._shared.policies_async import ExponentialRetry +from .._shared.uploads_async import upload_data_chunks, FileChunkUploader, IterStreamer +from .._shared.downloads_async import StorageStreamDownloader +from .._shared.base_client_async import AsyncStorageAccountHostsMixin +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 ..file_client import FileClient as FileClientBase +from ._polling_async import CloseHandlesAsync +from .models import HandlesPaged + +if TYPE_CHECKING: + from datetime import datetime + from ..models import ShareProperties, FilePermissions, ContentSettings, FileProperties + from .._generated.models import HandleItem + + +async def _upload_file_helper( + client, + stream, + size, + metadata, + content_settings, + validate_content, + timeout, + max_connections, + file_settings, + **kwargs): + try: + if size is None or size < 0: + raise ValueError("A content size must be specified for a File.") + response = await client.create_file( + size, + content_settings=content_settings, + metadata=metadata, + timeout=timeout, + **kwargs + ) + if size == 0: + return response + + return await upload_data_chunks( + service=client, + uploader_class=FileChunkUploader, + total_size=size, + chunk_size=file_settings.max_range_size, + stream=stream, + max_connections=max_connections, + validate_content=validate_content, + timeout=timeout, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + +class FileClient(AsyncStorageAccountHostsMixin, FileClientBase): + """A client to interact with a specific file, although that file may not yet exist. + + :ivar str url: + The full endpoint URL to the File, including SAS token if used. This could be + either the primary endpoint, or the secondard endpoint depending on the current `location_mode`. + :ivar str primary_endpoint: + The full primary endpoint URL. + :ivar str primary_hostname: + The hostname of the primary endpoint. + :ivar str secondary_endpoint: + The full secondard endpoint URL if configured. If not available + a ValueError will be raised. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str secondary_hostname: + The hostname of the secondary endpoint. If not available this + will be None. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str location_mode: + The location mode that the client is currently using. By default + this will be "primary". Options include "primary" and "secondary". + :param str file_url: The full URI to the file. This can also be a URL to the storage account + or share, in which case the file and/or share must also be specified. + :param share: The share for the file. If specified, this value will override + a share value specified in the file URL. + :type share: str or ~azure.storage.file.models.ShareProperties + :param str file_path: + The file path to the file with which to interact. If specified, this value will override + a file value specified in the file URL. + :param str snapshot: + An optional file snapshot on which to operate. + :param credential: + The credential with which to authenticate. This is optional if the + account URL already has a SAS token. The value can be a SAS token string or an account + shared access key. + """ + def __init__( # type: ignore + self, file_url, # type: str + share=None, # type: Optional[Union[str, ShareProperties]] + file_path=None, # type: Optional[str] + snapshot=None, # type: Optional[Union[str, Dict[str, Any]]] + credential=None, # type: Optional[Any] + loop=None, # type: Any + **kwargs # type: Any + ): + # type: (...) -> None + kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + super(FileClient, self).__init__( + file_url, + share=share, + file_path=file_path, + snapshot=snapshot, + credential=credential, + loop=loop, + **kwargs) + self._client = AzureFileStorage(version=VERSION, url=self.url, pipeline=self._pipeline, loop=loop) + self._loop = loop + + async def create_file( # type: ignore + self, size, # type: int + content_settings=None, # type: Optional[ContentSettings] + metadata=None, # type: Optional[Dict[str, str]] + timeout=None, # type: Optional[int] + **kwargs # type: Any + ): + # type: (...) -> Dict[str, Any] + """Creates a new file. + + Note that it only initializes the file with no content. + + :param int size: Specifies the maximum size for the file, + up to 1 TB. + :param ~azure.storage.file.models.ContentSettings content_settings: + ContentSettings object used to set file properties. + :param metadata: + Name-value pairs associated with the file as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: File-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_file.py + :start-after: [START create_file] + :end-before: [END create_file] + :language: python + :dedent: 12 + :caption: Create a file. + """ + if self.require_encryption and not self.key_encryption_key: + raise ValueError("Encryption required but no key was provided.") + + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) + file_http_headers = None + if content_settings: + file_http_headers = FileHTTPHeaders( + file_cache_control=content_settings.cache_control, + file_content_type=content_settings.content_type, + file_content_md5=bytearray(content_settings.content_md5) if content_settings.content_md5 else None, + file_content_encoding=content_settings.content_encoding, + file_content_language=content_settings.content_language, + file_content_disposition=content_settings.content_disposition + ) + try: + return await self._client.file.create( # type: ignore + file_content_length=size, + timeout=timeout, + metadata=metadata, + file_http_headers=file_http_headers, + headers=headers, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def upload_file( + self, data, # type: Any + length=None, # type: Optional[int] + metadata=None, # type: Optional[Dict[str, str]] + content_settings=None, # type: Optional[ContentSettings] + validate_content=False, # type: bool + max_connections=1, # type: Optional[int] + timeout=None, # type: Optional[int] + encoding='UTF-8', # type: str + **kwargs # type: Any + ): + # type: (...) -> Dict[str, Any] + """Uploads a new file. + + :param Any data: + Content of the file. + :param int length: + Length of the file in bytes. Specify its maximum size, up to 1 TiB. + :param metadata: + Name-value pairs associated with the file as metadata. + :type metadata: dict(str, str) + :param ~azure.storage.file.models.ContentSettings content_settings: + ContentSettings object used to set file properties. + :param bool validate_content: + If true, calculates an MD5 hash for each range of the file. The storage + service checks the hash of the content that has arrived with the hash + that was sent. This is primarily valuable for detecting bitflips on + the wire if using http instead of https as https (the default) will + already validate. Note that this MD5 hash is not stored with the + file. + :param int max_connections: + Maximum number of parallel connections to use. + :param int timeout: + The timeout parameter is expressed in seconds. + :param str encoding: + Defaults to UTF-8. + :returns: File-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_file.py + :start-after: [START upload_file] + :end-before: [END upload_file] + :language: python + :dedent: 12 + :caption: Upload a file. + """ + if self.require_encryption or (self.key_encryption_key is not None): + raise ValueError("Encryption not supported.") + + if isinstance(data, six.text_type): + data = data.encode(encoding) + if length is None: + length = get_length(data) + if isinstance(data, bytes): + data = data[:length] + + if isinstance(data, bytes): + stream = BytesIO(data) + elif hasattr(data, 'read'): + stream = data + elif hasattr(data, '__iter__'): + stream = IterStreamer(data, encoding=encoding) # type: ignore + else: + raise TypeError("Unsupported data type: {}".format(type(data))) + return await _upload_file_helper( # type: ignore + self, + stream, + length, + metadata, + content_settings, + validate_content, + timeout, + max_connections, + self._config, + **kwargs) + + async def start_copy_from_url( + self, source_url, # type: str + metadata=None, # type: Optional[Dict[str, str]] + timeout=None, # type: Optional[int] + **kwargs # type: Any + ): + # type: (...) -> Any + """Initiates the copying of data from a source URL into the file + referenced by the client. + + The status of this copy operation can be found using the `get_properties` + method. + + :param str source_url: + Specifies the URL of the source file. + :param metadata: + Name-value pairs associated with the file as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_file.py + :start-after: [START copy_file_from_url] + :end-before: [END copy_file_from_url] + :language: python + :dedent: 12 + :caption: Copy a file from a URL + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) + + try: + return await self._client.file.start_copy( + source_url, + timeout=timeout, + metadata=metadata, + headers=headers, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def abort_copy(self, copy_id, timeout=None, **kwargs): + # type: (Union[str, FileProperties], Optional[int], Any) -> Dict[str, Any] + """Abort an ongoing copy operation. + + This will leave a destination file with zero length and full metadata. + This will raise an error if the copy operation has already ended. + + :param copy_id: + The copy operation to abort. This can be either an ID, or an + instance of FileProperties. + :type copy_id: str or ~azure.storage.file.models.FileProperties + :rtype: None + """ + try: + copy_id = copy_id.copy.id + except AttributeError: + try: + copy_id = copy_id['copy_id'] + except TypeError: + pass + try: + await self._client.file.abort_copy(copy_id=copy_id, timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def download_file( + self, offset=None, # type: Optional[int] + length=None, # type: Optional[int] + validate_content=False, # type: bool + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> Iterable[bytes] + """Downloads a file to a stream with automatic chunking. + + :param int offset: + Start of byte range to use for downloading a section of the file. + Must be set if length is provided. + :param int length: + Number of bytes to read from the stream. This is optional, but + should be supplied for optimal performance. + :param bool validate_content: + If true, calculates an MD5 hash for each chunk of the file. The storage + service checks the hash of the content that has arrived with the hash + that was sent. This is primarily valuable for detecting bitflips on + the wire if using http instead of https as https (the default) will + already validate. Note that this MD5 hash is not stored with the + file. Also note that if enabled, the memory-efficient upload algorithm + will not be used, because computing the MD5 hash requires buffering + entire blocks, and doing so defeats the purpose of the memory-efficient algorithm. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: A iterable data generator (stream) + + Example: + .. literalinclude:: ../tests/test_file_samples_file.py + :start-after: [START download_file] + :end-before: [END download_file] + :language: python + :dedent: 12 + :caption: Download a file. + """ + if self.require_encryption or (self.key_encryption_key is not None): + raise ValueError("Encryption not supported.") + if length is not None and offset is None: + raise ValueError("Offset value must not be None is length is set.") + + downloader = StorageStreamDownloader( + service=self._client.file, + config=self._config, + offset=offset, + length=length, + validate_content=validate_content, + encryption_options=None, + cls=deserialize_file_stream, + timeout=timeout, + **kwargs) + await downloader.setup( + extra_properties={ + 'share': self.share_name, + 'name': self.file_name, + 'path': '/'.join(self.file_path), + }) + return downloader + + async def delete_file(self, timeout=None, **kwargs): + # type: (Optional[int], Optional[Any]) -> None + """Marks the specified file for deletion. The file is + later deleted during garbage collection. + + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_file.py + :start-after: [START delete_file] + :end-before: [END delete_file] + :language: python + :dedent: 12 + :caption: Delete a file. + """ + try: + await self._client.file.delete(timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def get_file_properties(self, timeout=None, **kwargs): + # type: (Optional[int], Any) -> FileProperties + """Returns all user-defined metadata, standard HTTP properties, and + system properties for the file. + + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: FileProperties + :rtype: ~azure.storage.file.models.FileProperties + """ + try: + file_props = await self._client.file.get_properties( + sharesnapshot=self.snapshot, + timeout=timeout, + cls=deserialize_file_properties, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + file_props.name = self.file_name + file_props.share = self.share_name + file_props.snapshot = self.snapshot + file_props.path = '/'.join(self.file_path) + return file_props # type: ignore + + async def set_http_headers(self, content_settings, timeout=None, **kwargs): # type: ignore + #type: (ContentSettings, Optional[int], Optional[Any]) -> Dict[str, Any] + """Sets HTTP headers on the file. + + :param ~azure.storage.file.models.ContentSettings content_settings: + ContentSettings object used to set file properties. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: File-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + """ + file_content_length = kwargs.pop('size', None) + file_http_headers = FileHTTPHeaders( + file_cache_control=content_settings.cache_control, + file_content_type=content_settings.content_type, + file_content_md5=bytearray(content_settings.content_md5) if content_settings.content_md5 else None, + file_content_encoding=content_settings.content_encoding, + file_content_language=content_settings.content_language, + file_content_disposition=content_settings.content_disposition + ) + try: + return await self._client.file.set_http_headers( # type: ignore + timeout=timeout, + file_content_length=file_content_length, + file_http_headers=file_http_headers, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def set_file_metadata(self, metadata=None, timeout=None, **kwargs): # type: ignore + #type: (Optional[Dict[str, Any]], Optional[int], Optional[Any]) -> Dict[str, Any] + """Sets user-defined metadata for the specified file as one or more + name-value pairs. + + Each call to this operation replaces all existing metadata + attached to the file. To remove all metadata from the file, + call this operation with no metadata dict. + + :param metadata: + Name-value pairs associated with the file as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: File-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) # type: ignore + try: + return await self._client.file.set_metadata( # type: ignore + timeout=timeout, + cls=return_response_headers, + headers=headers, + metadata=metadata, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def upload_range( # type: ignore + self, data, # type: bytes + start_range, # type: int + end_range, # type: int + validate_content=False, # type: Optional[bool] + timeout=None, # type: Optional[int] + encoding='UTF-8', + **kwargs + ): + # type: (...) -> Dict[str, Any] + """Upload a range of bytes to a file. + + :param bytes data: + The data to upload. + :param int start_range: + Start of byte range to use for uploading a section of the file. + The range can be up to 4 MB in size. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will upload first 512 bytes of file. + :param int end_range: + End of byte range to use for uploading a section of the file. + The range can be up to 4 MB in size. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will upload first 512 bytes of file. + :param bool validate_content: + If true, calculates an MD5 hash of the page content. The storage + service checks the hash of the content that has arrived + with the hash that was sent. This is primarily valuable for detecting + bitflips on the wire if using http instead of https as https (the default) + will already validate. Note that this MD5 hash is not stored with the + file. + :param int timeout: + The timeout parameter is expressed in seconds. + :param str encoding: + Defaults to UTF-8. + :returns: File-updated property dict (Etag and last modified). + :rtype: Dict[str, Any] + """ + if self.require_encryption or (self.key_encryption_key is not None): + raise ValueError("Encryption not supported.") + if isinstance(data, six.text_type): + data = data.encode(encoding) + + content_range = 'bytes={0}-{1}'.format(start_range, end_range) + content_length = end_range - start_range + 1 + try: + return await self._client.file.upload_range( # type: ignore + range=content_range, + content_length=content_length, + optionalbody=data, + timeout=timeout, + validate_content=validate_content, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def get_ranges( # type: ignore + self, start_range=None, # type: Optional[int] + end_range=None, # type: Optional[int] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> List[dict[str, int]] + """Returns the list of valid ranges of a file. + + :param int start_range: + Specifies the start offset of bytes over which to get ranges. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will download first 512 bytes of file. + :param int end_range: + Specifies the end offset of bytes over which to get ranges. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will download first 512 bytes of file. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: A list of valid ranges. + :rtype: List[dict[str, int]] + """ + if self.require_encryption or (self.key_encryption_key is not None): + raise ValueError("Unsupported method for encryption.") + + content_range = None + if start_range is not None: + if end_range is not None: + content_range = 'bytes={0}-{1}'.format(start_range, end_range) + else: + content_range = 'bytes={0}-'.format(start_range) + try: + ranges = await self._client.file.get_range_list( + sharesnapshot=self.snapshot, + timeout=timeout, + range=content_range, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + return [{'start': b.start, 'end': b.end} for b in ranges] + + async def clear_range( # type: ignore + self, start_range, # type: int + end_range, # type: int + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> Dict[str, Any] + """Clears the specified range and releases the space used in storage for + that range. + + :param int start_range: + Start of byte range to use for clearing a section of the file. + The range can be up to 4 MB in size. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will download first 512 bytes of file. + :param int end_range: + End of byte range to use for clearing a section of the file. + The range can be up to 4 MB in size. + The start_range and end_range params are inclusive. + Ex: start_range=0, end_range=511 will download first 512 bytes of file. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: File-updated property dict (Etag and last modified). + :rtype: Dict[str, Any] + """ + if self.require_encryption or (self.key_encryption_key is not None): + raise ValueError("Unsupported method for encryption.") + + if start_range is None or start_range % 512 != 0: + raise ValueError("start_range must be an integer that aligns with 512 file size") + if end_range is None or end_range % 512 != 511: + raise ValueError("end_range must be an integer that aligns with 512 file size") + content_range = 'bytes={0}-{1}'.format(start_range, end_range) + try: + return await self._client.file.upload_range( # type: ignore + timeout=timeout, + cls=return_response_headers, + content_length=0, + file_range_write="clear", + range=content_range, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def resize_file(self, size, timeout=None, **kwargs): # type: ignore + # type: (int, Optional[int], Optional[Any]) -> Dict[str, Any] + """Resizes a file to the specified size. + + :param int size: + Size to resize file to (in bytes) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: File-updated property dict (Etag and last modified). + :rtype: Dict[str, Any] + """ + try: + return await self._client.file.set_http_headers( # type: ignore + timeout=timeout, + file_content_length=size, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + def list_handles(self, marker=None, timeout=None, **kwargs): + """Lists handles for file. + + :param str marker: + An opaque continuation token. This value can be retrieved from the + next_marker field of a previous generator object. If specified, + this generator will begin returning results from this point. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: An auto-paging iterable of HandleItems + """ + results_per_page = kwargs.pop('results_per_page', None) + command = functools.partial( + self._client.file.list_handles, + sharesnapshot=self.snapshot, + timeout=timeout, + **kwargs) + return HandlesPaged( + command, results_per_page=results_per_page, marker=marker) + + async def close_handles( + self, handle=None, # type: Union[str, HandleItem] + timeout=None, # type: Optional[int] + **kwargs # type: Any + ): + # type: (...) -> Any + """Close open file handles. + + This operation may not finish with a single call, so a long-running poller + is returned that can be used to wait until the operation is complete. + + :param handle: + Optionally, a specific handle to close. The default value is '*' + which will attempt to close all open handles. + :type handle: str or ~azure.storage.file.models.Handle + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: A long-running poller to get operation status. + :rtype: ~azure.core.polling.LROPoller + """ + try: + handle_id = handle.id # type: ignore + except AttributeError: + handle_id = handle or '*' + command = functools.partial( + self._client.file.force_close_handles, + handle_id, + timeout=timeout, + sharesnapshot=self.snapshot, + cls=return_response_headers, + **kwargs) + try: + start_close = await command() + except StorageErrorException as error: + process_storage_error(error) + + polling_method = CloseHandlesAsync(self._config.copy_polling_interval) + return async_poller( + command, + start_close, + None, + polling_method) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/file_service_client_async.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/file_service_client_async.py new file mode 100644 index 000000000000..ffbb134c5a36 --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/file_service_client_async.py @@ -0,0 +1,299 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import functools +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, + TYPE_CHECKING +) + +from .._shared.base_client_async import AsyncStorageAccountHostsMixin +from .._shared.response_handlers import process_storage_error +from .._shared.policies_async import ExponentialRetry +from .._generated.aio import AzureFileStorage +from .._generated.models import StorageErrorException, StorageServiceProperties +from .._generated.version import VERSION +from ..file_service_client import FileServiceClient as FileServiceClientBase +from .share_client_async import ShareClient +from .models import SharePropertiesPaged + +if TYPE_CHECKING: + from datetime import datetime + from .._shared.models import ResourceTypes, AccountPermissions + from ..models import Metrics, CorsRule, ShareProperties + + +class FileServiceClient(AsyncStorageAccountHostsMixin, FileServiceClientBase): + """A client to interact with the File Service at the account level. + + This client provides operations to retrieve and configure the account properties + as well as list, create and delete shares within the account. + For operations relating to a specific share, a client for that entity + can also be retrieved using the `get_share_client` function. + + :ivar str url: + The full endpoint URL to the file service endpoint, including SAS token if used. This could be + either the primary endpoint, or the secondard endpoint depending on the current `location_mode`. + :ivar str primary_endpoint: + The full primary endpoint URL. + :ivar str primary_hostname: + The hostname of the primary endpoint. + :ivar str secondary_endpoint: + The full secondard endpoint URL if configured. If not available + a ValueError will be raised. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str secondary_hostname: + The hostname of the secondary endpoint. If not available this + will be None. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str location_mode: + The location mode that the client is currently using. By default + this will be "primary". Options include "primary" and "secondary". + :param str account_url: + The URL to the file storage account. Any other entities included + in the URL path (e.g. share or file) will be discarded. This URL can be optionally + authenticated with a SAS token. + :param credential: + The credential with which to authenticate. This is optional if the + account URL already has a SAS token. The value can be a SAS token string or an account + shared access key. + + Example: + .. literalinclude:: ../tests/test_file_samples_authentication.py + :start-after: [START create_file_service_client] + :end-before: [END create_file_service_client] + :language: python + :dedent: 8 + :caption: Create the file service client with url and credential. + """ + def __init__( + self, account_url, # type: str + credential=None, # type: Optional[Any] + loop=None, # type: Any + **kwargs # type: Any + ): + # type: (...) -> None + kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + super(FileServiceClient, self).__init__( + account_url, + credential=credential, + loop=loop, + **kwargs) + self._client = AzureFileStorage(version=VERSION, url=self.url, pipeline=self._pipeline, loop=loop) + self._loop = loop + + async def get_service_properties(self, timeout=None, **kwargs): + # type(Optional[int]) -> Dict[str, Any] + """Gets the properties of a storage account's File service, including + Azure Storage Analytics. + + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: ~azure.storage.file._generated.models.StorageServiceProperties + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START get_service_properties] + :end-before: [END get_service_properties] + :language: python + :dedent: 8 + :caption: Get file service properties. + """ + try: + return await self._client.service.get_properties(timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def set_service_properties( + self, hour_metrics=None, # type: Optional[Metrics] + minute_metrics=None, # type: Optional[Metrics] + cors=None, # type: Optional[List[CorsRule]] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> None + """Sets the properties of a storage account's File service, including + Azure Storage Analytics. If an element (e.g. Logging) is left as None, the + existing settings on the service for that functionality are preserved. + + :param hour_metrics: + The hour metrics settings provide a summary of request + statistics grouped by API in hourly aggregates for files. + :type hour_metrics: ~azure.storage.file.models.Metrics + :param minute_metrics: + The minute metrics settings provide request statistics + for each minute for files. + :type minute_metrics: ~azure.storage.file.models.Metrics + :param cors: + You can include up to five CorsRule elements in the + list. If an empty list is specified, all CORS rules will be deleted, + and CORS will be disabled for the service. + :type cors: list(:class:`~azure.storage.file.models.CorsRule`) + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START set_service_properties] + :end-before: [END set_service_properties] + :language: python + :dedent: 8 + :caption: Sets file service properties. + """ + props = StorageServiceProperties( + hour_metrics=hour_metrics, + minute_metrics=minute_metrics, + cors=cors + ) + try: + await self._client.service.set_properties(props, timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + def list_shares( + self, name_starts_with=None, # type: Optional[str] + include_metadata=False, # type: Optional[bool] + include_snapshots=False, # type: Optional[bool] + marker=None, + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> SharePropertiesPaged + """Returns auto-paging iterable of dict-like ShareProperties under the specified account. + The generator will lazily follow the continuation tokens returned by + the service and stop when all shares have been returned. + + :param str name_starts_with: + Filters the results to return only shares whose names + begin with the specified name_starts_with. + :param bool include_metadata: + Specifies that share metadata be returned in the response. + :param bool include_snapshots: + Specifies that share snapshot be returned in the response. + :param str marker: + An opaque continuation token. This value can be retrieved from the + next_marker field of a previous generator object. If specified, + this generator will begin returning results from this point. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: An iterable (auto-paging) of ShareProperties. + :rtype: ~azure.storage.file.models.SharePropertiesPaged + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START fsc_list_shares] + :end-before: [END fsc_list_shares] + :language: python + :dedent: 12 + :caption: List shares in the file service. + """ + include = [] + if include_metadata: + include.append('metadata') + if include_snapshots: + include.append('snapshots') + results_per_page = kwargs.pop('results_per_page', None) + command = functools.partial( + self._client.service.list_shares_segment, + include=include, + timeout=timeout, + **kwargs) + return SharePropertiesPaged( + command, prefix=name_starts_with, results_per_page=results_per_page, marker=marker) + + async def create_share( + self, share_name, # type: str + metadata=None, # type: Optional[Dict[str, str]] + quota=None, # type: Optional[int] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> ShareClient + """Creates a new share under the specified account. If the share + with the same name already exists, the operation fails. Returns a client with + which to interact with the newly created share. + + :param str share_name: The name of the share to create. + :param metadata: + A dict with name_value pairs to associate with the + share as metadata. Example:{'Category':'test'} + :type metadata: dict(str, str) + :param int quota: + Quota in bytes. + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: ~azure.storage.file.share_client.ShareClient + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START fsc_create_shares] + :end-before: [END fsc_create_shares] + :language: python + :dedent: 8 + :caption: Create a share in the file service. + """ + share = self.get_share_client(share_name) + await share.create_share(metadata, quota, timeout, **kwargs) + return share + + async def delete_share( + self, share_name, # type: Union[ShareProperties, str] + delete_snapshots=False, # type: Optional[bool] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> None + """Marks the specified share for deletion. The share is + later deleted during garbage collection. + + :param share_name: + The share to delete. This can either be the name of the share, + or an instance of ShareProperties. + :type share_name: str or ~azure.storage.file.models.ShareProperties + :param bool delete_snapshots: + Indicates if snapshots are to be deleted. + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START fsc_delete_shares] + :end-before: [END fsc_delete_shares] + :language: python + :dedent: 12 + :caption: Delete a share in the file service. + """ + share = self.get_share_client(share_name) + await share.delete_share( + delete_snapshots=delete_snapshots, timeout=timeout, **kwargs) + + def get_share_client(self, share, snapshot=None): + # type: (Union[ShareProperties, str],Optional[Union[Dict[str, Any], str]]) -> ShareClient + """Get a client to interact with the specified share. + The share need not already exist. + + :param share: + The share. This can either be the name of the share, + or an instance of ShareProperties. + :type share: str or ~azure.storage.file.models.ShareProperties + :param str snapshot: + An optional share snapshot on which to operate. + :returns: A ShareClient. + :rtype: ~azure.storage.file.share_client.ShareClient + + Example: + .. literalinclude:: ../tests/test_file_samples_service.py + :start-after: [START get_share_client] + :end-before: [END get_share_client] + :language: python + :dedent: 8 + :caption: Gets the share client. + """ + return ShareClient( + self.url, share=share, snapshot=snapshot, credential=self.credential, _hosts=self._hosts, + _configuration=self._config, _pipeline=self._pipeline, _location_mode=self._location_mode, loop=self._loop) diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/models.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/models.py new file mode 100644 index 000000000000..468eb076c2ce --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/models.py @@ -0,0 +1,186 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +# pylint: disable=too-few-public-methods, too-many-instance-attributes +# pylint: disable=super-init-not-called, too-many-lines + +from azure.core.paging import Paged + +from .._shared.response_handlers import return_context_and_deserialized, process_storage_error +from .._generated.models import StorageErrorException +from .._generated.models import DirectoryItem +from ..models import Handle, ShareProperties + + +def _wrap_item(item): + if isinstance(item, DirectoryItem): + return {'name': item.name, 'is_directory': True} + return {'name': item.name, 'size': item.properties.content_length, 'is_directory': False} + + +class SharePropertiesPaged(Paged): + """An iterable of Share properties. + + :ivar str service_endpoint: The service URL. + :ivar str prefix: A file name prefix being used to filter the list. + :ivar str current_marker: The continuation token of the current page of results. + :ivar int results_per_page: The maximum number of results retrieved per API call. + :ivar str next_marker: The continuation token to retrieve the next page of results. + :ivar str location_mode: The location mode being used to list results. The available + options include "primary" and "secondary". + :ivar current_page: The current page of listed results. + :vartype current_page: list(~azure.storage.file.models.ShareProperties) + + :param callable command: Function to retrieve the next page of items. + :param str prefix: Filters the results to return only shares whose names + begin with the specified prefix. + :param int results_per_page: The maximum number of share names to retrieve per + call. + :param str marker: An opaque continuation token. + """ + def __init__(self, command, prefix=None, results_per_page=None, marker=None, **kwargs): + super(SharePropertiesPaged, self).__init__(None, None, async_command=command) + self.service_endpoint = None + self.prefix = prefix + self.current_marker = None + self.results_per_page = results_per_page + self.next_marker = marker or "" + self.location_mode = None + + async def _async_advance_page(self): + """Force moving the cursor to the next azure call. + This method is for advanced usage, iterator protocol is prefered. + :raises: StopAsyncIteration if no further page + :return: The current page list + :rtype: list + """ + if self.next_marker is None: + raise StopAsyncIteration("End of paging") + self._current_page_iter_index = 0 + try: + self.location_mode, self._response = await self._async_get_next( + marker=self.next_marker or None, + maxresults=self.results_per_page, + cls=return_context_and_deserialized, + use_location=self.location_mode) + except StorageErrorException as error: + process_storage_error(error) + + self.service_endpoint = self._response.service_endpoint + self.prefix = self._response.prefix + self.current_marker = self._response.marker + self.results_per_page = self._response.max_results + self.current_page = [ShareProperties._from_generated(i) for i in self._response.share_items] # pylint: disable=protected-access + self.next_marker = self._response.next_marker or None + return self.current_page + + +class HandlesPaged(Paged): + """An iterable of Handles. + + :ivar str current_marker: The continuation token of the current page of results. + :ivar int results_per_page: The maximum number of results retrieved per API call. + :ivar str next_marker: The continuation token to retrieve the next page of results. + :ivar str location_mode: The location mode being used to list results. The available + options include "primary" and "secondary". + :ivar current_page: The current page of listed results. + :vartype current_page: list(~azure.storage.file.models.Handle) + + :param callable command: Function to retrieve the next page of items. + :param int results_per_page: The maximum number of share names to retrieve per + call. + :param str marker: An opaque continuation token. + """ + def __init__(self, command, results_per_page=None, marker=None, **kwargs): + super(HandlesPaged, self).__init__(None, None, async_command=command) + self.current_marker = None + self.results_per_page = results_per_page + self.next_marker = marker or "" + self.location_mode = None + + async def _async_advance_page(self): + """Force moving the cursor to the next azure call. + This method is for advanced usage, iterator protocol is prefered. + :raises: StopAsyncIteration if no further page + :return: The current page list + :rtype: list + """ + if self.next_marker is None: + raise StopAsyncIteration("End of paging") + self._current_page_iter_index = 0 + try: + self.location_mode, self._response = await self._async_get_next( + marker=self.next_marker or None, + maxresults=self.results_per_page, + cls=return_context_and_deserialized, + use_location=self.location_mode) + except StorageErrorException as error: + process_storage_error(error) + self.current_page = [Handle._from_generated(h) for h in self._response.handle_list] # pylint: disable=protected-access + self.next_marker = self._response.next_marker or None + return self.current_page + + +class DirectoryPropertiesPaged(Paged): + """An iterable for the contents of a directory. + + This iterable will yield dicts for the contents of the directory. The dicts + will have the keys 'name' (str) and 'is_directory' (bool). + Items that are files (is_directory=False) will have an additional 'content_length' key. + + :ivar str service_endpoint: The service URL. + :ivar str prefix: A file name prefix being used to filter the list. + :ivar str current_marker: The continuation token of the current page of results. + :ivar int results_per_page: The maximum number of results retrieved per API call. + :ivar str next_marker: The continuation token to retrieve the next page of results. + :ivar str location_mode: The location mode being used to list results. The available + options include "primary" and "secondary". + :ivar current_page: The current page of listed results. + :vartype current_page: list(dict(str, Any)) + + :param callable command: Function to retrieve the next page of items. + :param str prefix: Filters the results to return only directories whose names + begin with the specified prefix. + :param int results_per_page: The maximum number of share names to retrieve per + call. + :param str marker: An opaque continuation token. + """ + def __init__(self, command, prefix=None, results_per_page=None, marker=None, **kwargs): + super(DirectoryPropertiesPaged, self).__init__(None, None, async_command=command) + self.service_endpoint = None + self.prefix = prefix + self.current_marker = None + self.results_per_page = results_per_page + self.next_marker = marker or "" + self.location_mode = None + + async def _async_advance_page(self): + """Force moving the cursor to the next azure call. + This method is for advanced usage, iterator protocol is prefered. + :raises: StopAsyncIteration if no further page + :return: The current page list + :rtype: list + """ + if self.next_marker is None: + raise StopAsyncIteration("End of paging") + self._current_page_iter_index = 0 + try: + self.location_mode, self._response = await self._async_get_next( + marker=self.next_marker or None, + prefix=self.prefix, + maxresults=self.results_per_page, + cls=return_context_and_deserialized, + use_location=self.location_mode) + except StorageErrorException as error: + process_storage_error(error) + + self.service_endpoint = self._response.service_endpoint + self.prefix = self._response.prefix + self.current_marker = self._response.marker + self.results_per_page = self._response.max_results + self.current_page = [_wrap_item(i) for i in self._response.segment.directory_items] + self.current_page.extend([_wrap_item(i) for i in self._response.segment.file_items]) + self.next_marker = self._response.next_marker or None + return self.current_page diff --git a/sdk/storage/azure-storage-file/azure/storage/file/aio/share_client_async.py b/sdk/storage/azure-storage-file/azure/storage/file/aio/share_client_async.py new file mode 100644 index 000000000000..18b677c288af --- /dev/null +++ b/sdk/storage/azure-storage-file/azure/storage/file/aio/share_client_async.py @@ -0,0 +1,462 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Optional, Union, Dict, Any, TYPE_CHECKING +) + +from .._shared.policies_async import ExponentialRetry +from .._shared.base_client_async import AsyncStorageAccountHostsMixin +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 .._generated.aio import AzureFileStorage +from .._generated.version import VERSION +from .._generated.models import ( + StorageErrorException, + SignedIdentifier, + DeleteSnapshotsOptionType) +from .._deserialize import deserialize_share_properties +from ..share_client import ShareClient as ShareClientBase +from .directory_client_async import DirectoryClient +from .file_client_async import FileClient + +if TYPE_CHECKING: + from ..models import ShareProperties, AccessPolicy + + +class ShareClient(AsyncStorageAccountHostsMixin, ShareClientBase): + """A client to interact with a specific share, although that share may not yet exist. + + For operations relating to a specific directory or file in this share, the clients for + those entities can also be retrieved using the `get_directory_client` and `get_file_client` functions. + + :ivar str url: + The full endpoint URL to the Share, including snapshot and SAS token if used. This could be + either the primary endpoint, or the secondard endpoint depending on the current `location_mode`. + :ivar str primary_endpoint: + The full primary endpoint URL. + :ivar str primary_hostname: + The hostname of the primary endpoint. + :ivar str secondary_endpoint: + The full secondard endpoint URL if configured. If not available + a ValueError will be raised. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str secondary_hostname: + The hostname of the secondary endpoint. If not available this + will be None. To explicitly specify a secondary hostname, use the optional + `secondary_hostname` keyword argument on instantiation. + :ivar str location_mode: + The location mode that the client is currently using. By default + this will be "primary". Options include "primary" and "secondary". + :param str share_url: The full URI to the share. + :param share: The share with which to interact. If specified, this value will override + a share value specified in the share URL. + :type share: str or ~azure.storage.file.models.ShareProperties + :param str snapshot: + An optional share snapshot on which to operate. + :param credential: + The credential with which to authenticate. This is optional if the + account URL already has a SAS token. The value can be a SAS token string or an account + shared access key. + """ + def __init__( # type: ignore + self, share_url, # type: str + share=None, # type: Optional[Union[str, ShareProperties]] + snapshot=None, # type: Optional[Union[str, Dict[str, Any]]] + credential=None, # type: Optional[Any] + loop=None, # type: Any + **kwargs # type: Any + ): + # type: (...) -> None + kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + super(ShareClient, self).__init__( + share_url, + share=share, + snapshot=snapshot, + credential=credential, + loop=loop, + **kwargs) + self._client = AzureFileStorage(version=VERSION, url=self.url, pipeline=self._pipeline, loop=loop) + self._loop = loop + + def get_directory_client(self, directory_path=None): + """Get a client to interact with the specified directory. + The directory need not already exist. + + :param str directory_path: + Path to the specified directory. + :returns: A Directory Client. + :rtype: ~azure.storage.file.directory_client.DirectoryClient + """ + return DirectoryClient( + self.url, directory_path=directory_path or "", snapshot=self.snapshot, credential=self.credential, + _hosts=self._hosts, _configuration=self._config, _pipeline=self._pipeline, + _location_mode=self._location_mode, loop=self._loop) + + def get_file_client(self, file_path): + """Get a client to interact with the specified file. + The file need not already exist. + + :param str file_path: + Path to the specified file. + :returns: A File Client. + :rtype: ~azure.storage.file.file_client.FileClient + """ + return FileClient( + self.url, file_path=file_path, snapshot=self.snapshot, credential=self.credential, _hosts=self._hosts, + _configuration=self._config, _pipeline=self._pipeline, _location_mode=self._location_mode, loop=self._loop) + + async def create_share( # type: ignore + self, metadata=None, # type: Optional[Dict[str, str]] + quota=None, # type: Optional[int] + timeout=None, # type: Optional[int] + **kwargs # type: Optional[Any] + ): + # type: (...) -> Dict[str, Any] + """Creates a new Share under the account. If a share with the + same name already exists, the operation fails. + + :param metadata: + Name-value pairs associated with the share as metadata. + :type metadata: dict(str, str) + :param int quota: + The quota to be allotted. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Share-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START create_share] + :end-before: [END create_share] + :language: python + :dedent: 8 + :caption: Creates a file share. + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) # type: ignore + + try: + return await self._client.share.create( # type: ignore + timeout=timeout, + metadata=metadata, + quota=quota, + cls=return_response_headers, + headers=headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def create_snapshot( # type: ignore + self, metadata=None, # type: Optional[Dict[str, str]] + timeout=None, # type: Optional[int] + **kwargs # type: Optional[Any] + ): + # type: (...) -> Dict[str, Any] + """Creates a snapshot of the share. + + A snapshot is a read-only version of a share that's taken at a point in time. + It can be read, copied, or deleted, but not modified. Snapshots provide a way + to back up a share as it appears at a moment in time. + + A snapshot of a share has the same name as the base share from which the snapshot + is taken, with a DateTime value appended to indicate the time at which the + snapshot was taken. + + :param metadata: + Name-value pairs associated with the share as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Share-updated property dict (Snapshot ID, Etag, and last modified). + :rtype: dict[str, Any] + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START create_share_snapshot] + :end-before: [END create_share_snapshot] + :language: python + :dedent: 12 + :caption: Creates a snapshot of the file share. + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) # type: ignore + try: + return await self._client.share.create_snapshot( # type: ignore + timeout=timeout, + cls=return_response_headers, + headers=headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def delete_share( + self, delete_snapshots=False, # type: Optional[bool] + timeout=None, # type: Optional[int] + **kwargs + ): + # type: (...) -> None + """Marks the specified share for deletion. The share is + later deleted during garbage collection. + + :param bool delete_snapshots: + Indicates if snapshots are to be deleted. + :param int timeout: + The timeout parameter is expressed in seconds. + :rtype: None + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START delete_share] + :end-before: [END delete_share] + :language: python + :dedent: 12 + :caption: Deletes the share and any snapshots. + """ + delete_include = None + if delete_snapshots: + delete_include = DeleteSnapshotsOptionType.include + try: + await self._client.share.delete( + timeout=timeout, + sharesnapshot=self.snapshot, + delete_snapshots=delete_include, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def get_share_properties(self, timeout=None, **kwargs): + # type: (Optional[int], Any) -> ShareProperties + """Returns all user-defined metadata and system properties for the + specified share. The data returned does not include the shares's + list of files or directories. + + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: The share properties. + :rtype: ~azure.storage.file.models.ShareProperties + + Example: + .. literalinclude:: ../tests/test_file_samples_hello_world.py + :start-after: [START get_share_properties] + :end-before: [END get_share_properties] + :language: python + :dedent: 12 + :caption: Gets the share properties. + """ + try: + props = await self._client.share.get_properties( + timeout=timeout, + sharesnapshot=self.snapshot, + cls=deserialize_share_properties, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + props.name = self.share_name + props.snapshot = self.snapshot + return props # type: ignore + + async def set_share_quota(self, quota, timeout=None, **kwargs): # type: ignore + # type: (int, Optional[int], Any) -> Dict[str, Any] + """Sets the quota for the share. + + :param int quota: + Specifies the maximum size of the share, in gigabytes. + Must be greater than 0, and less than or equal to 5TB. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Share-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START set_share_quota] + :end-before: [END set_share_quota] + :language: python + :dedent: 12 + :caption: Sets the share quota. + """ + try: + return await self._client.share.set_quota( # type: ignore + timeout=timeout, + quota=quota, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def set_share_metadata(self, metadata, timeout=None, **kwargs): # type: ignore + # type: (Dict[str, Any], Optional[int], Any) -> Dict[str, Any] + """Sets the metadata for the share. + + Each call to this operation replaces all existing metadata + attached to the share. To remove all metadata from the share, + call this operation with no metadata dict. + + :param metadata: + Name-value pairs associated with the share as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Share-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START set_share_metadata] + :end-before: [END set_share_metadata] + :language: python + :dedent: 12 + :caption: Sets the share metadata. + """ + headers = kwargs.pop('headers', {}) + headers.update(add_metadata_headers(metadata)) + try: + return await self._client.share.set_metadata( # type: ignore + timeout=timeout, + cls=return_response_headers, + headers=headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def get_share_access_policy(self, timeout=None, **kwargs): + # type: (Optional[int], **Any) -> Dict[str, Any] + """Gets the permissions for the share. The permissions + indicate whether files in a share may be accessed publicly. + + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Access policy information in a dict. + :rtype: dict[str, str] + """ + try: + response, identifiers = await self._client.share.get_access_policy( + timeout=timeout, + cls=return_headers_and_deserialized, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + return { + 'public_access': response.get('share_public_access'), + 'signed_identifiers': identifiers or [] + } + + async def set_share_access_policy(self, signed_identifiers=None, timeout=None, **kwargs): # type: ignore + # type: (Optional[Dict[str, Optional[AccessPolicy]]], Optional[int], **Any) -> Dict[str, str] + """Sets the permissions for the share, or stored access + policies that may be used with Shared Access Signatures. The permissions + indicate whether files in a share may be accessed publicly. + + :param signed_identifiers: + A dictionary of access policies to associate with the share. The + dictionary may contain up to 5 elements. An empty dictionary + will clear the access policies set on the service. + :type signed_identifiers: dict(str, :class:`~azure.storage.file.models.AccessPolicy`) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: Share-updated property dict (Etag and last modified). + :rtype: dict(str, Any) + """ + if signed_identifiers: + if len(signed_identifiers) > 5: + raise ValueError( + 'Too many access policies provided. The server does not support setting ' + 'more than 5 access policies on a single resource.') + identifiers = [] + for key, value in signed_identifiers.items(): + if value: + value.start = serialize_iso(value.start) + value.expiry = serialize_iso(value.expiry) + identifiers.append(SignedIdentifier(id=key, access_policy=value)) + signed_identifiers = identifiers # type: ignore + + try: + return await self._client.share.set_access_policy( # type: ignore + share_acl=signed_identifiers or None, + timeout=timeout, + cls=return_response_headers, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + async def get_share_stats(self, timeout=None, **kwargs): # type: ignore + # type: (Optional[int], **Any) -> int + """Gets the approximate size of the data stored on the share in bytes. + + Note that this value may not include all recently created + or recently re-sized files. + + :param int timeout: + The timeout parameter is expressed in seconds. + :return: The approximate size of the data (in bytes) stored on the share. + :rtype: int + """ + try: + stats = await self._client.share.get_statistics( + timeout=timeout, + **kwargs) + return stats.share_usage_bytes # type: ignore + except StorageErrorException as error: + process_storage_error(error) + + def list_directories_and_files( # type: ignore + self, directory_name=None, # type: Optional[str] + name_starts_with=None, # type: Optional[str] + marker=None, # type: Optional[str] + timeout=None, # type: Optional[int] + **kwargs # type: Any + ): + # type: (...) -> Iterable[dict[str,str]] + """Lists the directories and files under the share. + + :param str directory_name: + Name of a directory. + :param str name_starts_with: + Filters the results to return only directories whose names + begin with the specified prefix. + :param str marker: + An opaque continuation token. This value can be retrieved from the + next_marker field of a previous generator object. If specified, + this generator will begin returning results from this point. + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: An auto-paging iterable of dict-like DirectoryProperties and FileProperties + + Example: + .. literalinclude:: ../tests/test_file_samples_share.py + :start-after: [START share_list_files_in_dir] + :end-before: [END share_list_files_in_dir] + :language: python + :dedent: 12 + :caption: List directories and files in the share. + """ + directory = self.get_directory_client(directory_name) + return directory.list_directories_and_files( + name_starts_with=name_starts_with, marker=marker, timeout=timeout, **kwargs) + + async def create_directory(self, directory_name, metadata=None, timeout=None, **kwargs): + # type: (str, Optional[Dict[str, Any]], Optional[int], Any) -> DirectoryClient + """Creates a directory in the share and returns a client to interact + with the directory. + + :param str directory_name: + The name of the directory. + :param metadata: + Name-value pairs associated with the directory as metadata. + :type metadata: dict(str, str) + :param int timeout: + The timeout parameter is expressed in seconds. + :returns: DirectoryClient + :rtype: ~azure.storage.file.directory_client.DirectoryClient + """ + directory = self.get_directory_client(directory_name) + await directory.create_directory(metadata, timeout, **kwargs) + return directory # type: ignore diff --git a/sdk/storage/azure-storage-file/azure/storage/file/directory_client.py b/sdk/storage/azure-storage-file/azure/storage/file/directory_client.py index 7e0bb4f4885c..3922153aebd3 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/directory_client.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/directory_client.py @@ -17,22 +17,16 @@ import six from azure.core.polling import LROPoller -from .file_client import FileClient - -from .models import DirectoryPropertiesPaged, HandlesPaged from ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import StorageErrorException -from ._shared.utils import ( - StorageAccountHostsMixin, - parse_query, - return_response_headers, - add_metadata_headers, - process_storage_error, - parse_connection_str) - -from ._share_utils import deserialize_directory_properties -from .polling import CloseHandles +from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query +from ._shared.request_handlers import add_metadata_headers +from ._shared.response_handlers import return_response_headers, process_storage_error +from ._deserialize import deserialize_directory_properties +from ._polling import CloseHandles +from .file_client import FileClient +from .models import DirectoryPropertiesPaged, HandlesPaged if TYPE_CHECKING: from .models import SharePermissions, ShareProperties, DirectoryProperties, ContentSettings @@ -368,7 +362,7 @@ def close_handles( except StorageErrorException as error: process_storage_error(error) - polling_method = CloseHandles(self._config.data_settings.copy_polling_interval) + polling_method = CloseHandles(self._config.copy_polling_interval) return LROPoller( command, start_close, diff --git a/sdk/storage/azure-storage-file/azure/storage/file/file_client.py b/sdk/storage/azure-storage-file/azure/storage/file/file_client.py index ec492fe25197..af3294a258cb 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 @@ -19,28 +19,63 @@ import six from azure.core.polling import LROPoller -from .models import HandlesPaged from ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import StorageErrorException, FileHTTPHeaders -from ._shared.upload_chunking import IterStreamer +from ._shared.uploads import IterStreamer, FileChunkUploader, upload_data_chunks +from ._shared.downloads import StorageStreamDownloader from ._shared.shared_access_signature import FileSharedAccessSignature -from ._shared.utils import ( - StorageAccountHostsMixin, - parse_query, - get_length, - return_response_headers, - add_metadata_headers, - process_storage_error, - parse_connection_str) -from ._share_utils import upload_file_helper, deserialize_file_properties, StorageStreamDownloader -from .polling import CopyStatusPoller, CloseHandles +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 if TYPE_CHECKING: from datetime import datetime from .models import ShareProperties, FilePermissions, ContentSettings, FileProperties from ._generated.models import HandleItem + +def _upload_file_helper( + client, + stream, + size, + metadata, + content_settings, + validate_content, + timeout, + max_connections, + file_settings, + **kwargs): + try: + if size is None or size < 0: + raise ValueError("A content size must be specified for a File.") + response = client.create_file( + size, + content_settings=content_settings, + metadata=metadata, + timeout=timeout, + **kwargs + ) + if size == 0: + return response + + return upload_data_chunks( + service=client, + uploader_class=FileChunkUploader, + total_size=size, + chunk_size=file_settings.max_range_size, + stream=stream, + max_connections=max_connections, + validate_content=validate_content, + timeout=timeout, + **kwargs) + except StorageErrorException as error: + process_storage_error(error) + + class FileClient(StorageAccountHostsMixin): """A client to interact with a specific file, although that file may not yet exist. @@ -402,7 +437,7 @@ def upload_file( stream = IterStreamer(data, encoding=encoding) # type: ignore else: raise TypeError("Unsupported data type: {}".format(type(data))) - return upload_file_helper( # type: ignore + return _upload_file_helper( # type: ignore self, stream, length, @@ -411,18 +446,21 @@ def upload_file( validate_content, timeout, max_connections, - self._config.data_settings, + self._config, **kwargs) - def copy_file_from_url( + def start_copy_from_url( self, source_url, # type: str metadata=None, # type: Optional[Dict[str, str]] timeout=None, # type: Optional[int] **kwargs # type: Any ): # type: (...) -> Any - """Copies the file from the provided URL to the file referenced by - the client. + """Initiates the copying of data from a source URL into the file + referenced by the client. + + The status of this copy operation can be found using the `get_properties` + method. :param str source_url: Specifies the URL of the source file. @@ -431,8 +469,7 @@ def copy_file_from_url( :type metadata: dict(str, str) :param int timeout: The timeout parameter is expressed in seconds. - :returns: Polling object in order to wait on or abort the operation - :rtype: ~azure.storage.file.polling.CopyStatusPoller + :rtype: dict(str, Any) Example: .. literalinclude:: ../tests/test_file_samples_file.py @@ -446,9 +483,9 @@ def copy_file_from_url( headers.update(add_metadata_headers(metadata)) try: - start_copy = self._client.file.start_copy( + return self._client.file.start_copy( source_url, - timeout=None, + timeout=timeout, metadata=metadata, headers=headers, cls=return_response_headers, @@ -456,11 +493,30 @@ def copy_file_from_url( except StorageErrorException as error: process_storage_error(error) - poller = CopyStatusPoller( - self, start_copy, - configuration=self._config, - timeout=timeout) - return poller + def abort_copy(self, copy_id, timeout=None, **kwargs): + # type: (Union[str, FileProperties], Optional[int], Any) -> Dict[str, Any] + """Abort an ongoing copy operation. + + This will leave a destination file with zero length and full metadata. + This will raise an error if the copy operation has already ended. + + :param copy_id: + The copy operation to abort. This can be either an ID, or an + instance of FileProperties. + :type copy_id: str or ~azure.storage.file.models.FileProperties + :rtype: None + """ + try: + copy_id = copy_id.copy.id + except AttributeError: + try: + copy_id = copy_id['copy_id'] + except TypeError: + pass + try: + self._client.file.abort_copy(copy_id=copy_id, timeout=timeout, **kwargs) + except StorageErrorException as error: + process_storage_error(error) def download_file( self, offset=None, # type: Optional[int] @@ -505,14 +561,18 @@ def download_file( raise ValueError("Offset value must not be None is length is set.") return StorageStreamDownloader( - share=self.share_name, - file_name=self.file_name, - file_path='/'.join(self.file_path), service=self._client.file, - config=self._config.data_settings, + config=self._config, offset=offset, length=length, validate_content=validate_content, + encryption_options=None, + extra_properties={ + 'share': self.share_name, + 'name': self.file_name, + 'path': '/'.join(self.file_path), + }, + cls=deserialize_file_stream, timeout=timeout, **kwargs) @@ -557,7 +617,9 @@ def get_file_properties(self, timeout=None, **kwargs): except StorageErrorException as error: process_storage_error(error) file_props.name = self.file_name - file_props.share_name = self.share_name + file_props.share = self.share_name + file_props.snapshot = self.snapshot + file_props.path = '/'.join(self.file_path) return file_props # type: ignore def set_http_headers(self, content_settings, timeout=None, **kwargs): # type: ignore @@ -837,7 +899,7 @@ def close_handles( except StorageErrorException as error: process_storage_error(error) - polling_method = CloseHandles(self._config.data_settings.copy_polling_interval) + polling_method = CloseHandles(self._config.copy_polling_interval) return LROPoller( command, start_close, diff --git a/sdk/storage/azure-storage-file/azure/storage/file/file_service_client.py b/sdk/storage/azure-storage-file/azure/storage/file/file_service_client.py index ffde63e24f69..3c0ee513b260 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/file_service_client.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/file_service_client.py @@ -14,19 +14,16 @@ except ImportError: from urlparse import urlparse # type: ignore -from .share_client import ShareClient from ._shared.shared_access_signature import SharedAccessSignature from ._shared.models import Services -from ._shared.utils import ( - StorageAccountHostsMixin, - parse_connection_str, - process_storage_error, - parse_query) - -from .models import SharePropertiesPaged +from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query +from ._shared.response_handlers import process_storage_error from ._generated import AzureFileStorage from ._generated.models import StorageErrorException, StorageServiceProperties from ._generated.version import VERSION +from .share_client import ShareClient +from .models import SharePropertiesPaged + if TYPE_CHECKING: from datetime import datetime from ._shared.models import ResourceTypes, AccountPermissions diff --git a/sdk/storage/azure-storage-file/azure/storage/file/models.py b/sdk/storage/azure-storage-file/azure/storage/file/models.py index d77baed48d7d..019be036c739 100644 --- a/sdk/storage/azure-storage-file/azure/storage/file/models.py +++ b/sdk/storage/azure-storage-file/azure/storage/file/models.py @@ -7,10 +7,8 @@ # pylint: disable=super-init-not-called, too-many-lines from azure.core.paging import Paged -from ._shared.utils import ( - return_context_and_deserialized, - process_storage_error) +from ._shared.response_handlers import return_context_and_deserialized, process_storage_error from ._shared.models import DictMixin, get_enum_value from ._generated.models import StorageErrorException from ._generated.models import Metrics as GeneratedMetrics diff --git a/sdk/storage/azure-storage-file/azure/storage/file/polling.py b/sdk/storage/azure-storage-file/azure/storage/file/polling.py deleted file mode 100644 index f3ea0900c3c7..000000000000 --- a/sdk/storage/azure-storage-file/azure/storage/file/polling.py +++ /dev/null @@ -1,215 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import logging -import time -from typing import Any, Callable -from azure.core.polling import PollingMethod, LROPoller - -from ._shared.utils import process_storage_error -from ._generated.models import StorageErrorException -from ._share_utils import deserialize_file_properties - - -logger = logging.getLogger(__name__) - - -class CopyStatusPoller(LROPoller): - """Poller for a long-running copy operation.""" - - def __init__(self, client, copy_id, polling=True, configuration=None, **kwargs): - if configuration: - polling_interval = configuration.data_settings.copy_polling_interval - else: - polling_interval = 2 - polling_method = CopyFilePolling if polling else CopyFile - poller = polling_method(polling_interval, **kwargs) - super(CopyStatusPoller, self).__init__(client, copy_id, None, poller) - - def copy_id(self): - # type: () -> str - """Get the ID of the copy operation. - - :rtype: str - """ - return self._polling_method.id - - def abort(self): - # type: () -> None - """Abort the copy operation. - - This will leave a destination file with zero length and full metadata. - This will raise an error if the copy operation has already ended. - - :rtype: None - """ - return self._polling_method.abort() - - def status(self): # pylint: disable=useless-super-delegation - # type: () -> str - """Returns the current status of the copy operation. - - :rtype: str - """ - return super(CopyStatusPoller, self).status() - - def result(self, timeout=None): - # type: (Optional[int]) -> Model - """Return the FileProperties after the completion of the copy operation, - or the properties available after the specified timeout. - - :returns: The destination file properties. - :rtype: ~azure.storage.file.models.FileProperties - """ - return super(CopyStatusPoller, self).result(timeout=timeout) - - -class CopyFile(PollingMethod): - """An empty poller that returns the deserialized initial response. - """ - def __init__(self, interval, **kwargs): - self._client = None - self._status = None - self._exception = None - self.id = None - self.etag = None - self.last_modified = None - self.polling_interval = interval - self.kwargs = kwargs - self.file = None - - def _update_status(self): - try: - self.file = self._client._client.file.get_properties( # pylint: disable=protected-access - cls=deserialize_file_properties, **self.kwargs) - except StorageErrorException as error: - process_storage_error(error) - self._status = self.file.copy.status - self.etag = self.file.etag - self.last_modified = self.file.last_modified - - def initialize(self, client, initial_status, _): # pylint: disable=arguments-differ - # type: (Any, Any, Callable) -> None - self._client = client - if isinstance(initial_status, str): - self.id = initial_status - self._update_status() - else: - self._status = initial_status['copy_status'] - self.id = initial_status['copy_id'] - self.etag = initial_status['etag'] - self.last_modified = initial_status['last_modified'] - - def run(self): - # type: () -> None - """Empty run, no polling. - """ - - def abort(self): - try: - return self._client._client.file.abort_copy( # pylint: disable=protected-access - self.id, **self.kwargs) - except StorageErrorException as error: - process_storage_error(error) - - def status(self): - self._update_status() - return self._status - - def finished(self): - # type: () -> bool - """Is this polling finished? - :rtype: bool - """ - return str(self.status()).lower() in ['success', 'aborted', 'failed'] - - def resource(self): - # type: () -> Any - self._update_status() - return self.file - - -class CopyFilePolling(CopyFile): - - def run(self): - # type: () -> None - try: - while not self.finished(): - self._update_status() - time.sleep(self.polling_interval) - if str(self.status()).lower() == 'aborted': - raise ValueError("Copy operation aborted.") - if str(self.status()).lower() == 'failed': - raise ValueError("Copy operation failed: {}".format(self.file.copy.status_description)) - except Exception as e: - logger.warning(str(e)) - raise - - def status(self): - # type: () -> str - """Return the current status as a string. - :rtype: str - """ - try: - return self._status.value # type: ignore - except AttributeError: - return self._status # type: ignore - - def resource(self): - # type: () -> Any - if not self.file: - self._update_status() - return self.file - - -class CloseHandles(PollingMethod): - - def __init__(self, interval): - self._command = None - self._status = None - self._exception = None - self.handles_closed = 0 - self.polling_interval = interval - - def _update_status(self): - try: - status = self._command() # pylint: disable=protected-access - except StorageErrorException as error: - process_storage_error(error) - self._status = status.get('marker') - self.handles_closed += status['number_of_handles_closed'] - - def initialize(self, command, initial_status, _): # pylint: disable=arguments-differ - # type: (Any, Any, Callable) -> None - self._command = command - self._status = initial_status['marker'] - self.handles_closed = initial_status['number_of_handles_closed'] - - def run(self): - # type: () -> None - try: - while not self.finished(): - self._update_status() - time.sleep(self.polling_interval) - except Exception as e: - logger.warning(str(e)) - raise - - def status(self): - self._update_status() - return self.handles_closed - - def finished(self): - # type: () -> bool - """Is this polling finished? - :rtype: bool - """ - return self._status is None - - def resource(self): - # type: () -> Any - if not self.finished: - self._update_status() - return self.handles_closed diff --git a/sdk/storage/azure-storage-file/azure/storage/file/share_client.py b/sdk/storage/azure-storage-file/azure/storage/file/share_client.py index 28fb30aea66f..ff5b183f49be 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 @@ -16,25 +16,21 @@ import six from ._shared.shared_access_signature import FileSharedAccessSignature -from .directory_client import DirectoryClient -from .file_client import FileClient +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 ._generated import AzureFileStorage from ._generated.version import VERSION from ._generated.models import ( StorageErrorException, SignedIdentifier, DeleteSnapshotsOptionType) -from ._shared.utils import ( - StorageAccountHostsMixin, - serialize_iso, - return_headers_and_deserialized, - parse_query, - return_response_headers, - add_metadata_headers, - process_storage_error, - parse_connection_str) - -from ._share_utils import deserialize_share_properties +from ._deserialize import deserialize_share_properties +from .directory_client import DirectoryClient +from .file_client import FileClient if TYPE_CHECKING: from .models import ShareProperties, AccessPolicy @@ -312,8 +308,6 @@ def create_share( # type: ignore :dedent: 8 :caption: Creates a file share. """ - if self.require_encryption and not self.key_encryption_key: - raise ValueError("Encryption required but no key was provided.") headers = kwargs.pop('headers', {}) headers.update(add_metadata_headers(metadata)) # type: ignore diff --git a/sdk/storage/azure-storage-file/conftest.py b/sdk/storage/azure-storage-file/conftest.py new file mode 100644 index 000000000000..330109f55cd3 --- /dev/null +++ b/sdk/storage/azure-storage-file/conftest.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import sys +import pytest + + +# Ignore async tests for Python < 3.5 +collect_ignore_glob = [] +if sys.version_info < (3, 5): + collect_ignore_glob.append("tests/*_async.py") diff --git a/sdk/storage/azure-storage-file/dev_requirements.txt b/sdk/storage/azure-storage-file/dev_requirements.txt index 6ac6eff04942..4475ce1af614 100644 --- a/sdk/storage/azure-storage-file/dev_requirements.txt +++ b/sdk/storage/azure-storage-file/dev_requirements.txt @@ -1,5 +1,5 @@ -e ../../../tools/azure-sdk-tools -e ../../core/azure-core -pylint==2.1.1; python_version >= '3.4' +pylint==2.3.1; python_version >= '3.4' pylint==1.8.4; python_version < '3.4' aiohttp>=3.0; python_version >= '3.5' diff --git a/sdk/storage/azure-storage-file/tests/test_directory_async.py b/sdk/storage/azure-storage-file/tests/test_directory_async.py new file mode 100644 index 000000000000..dae655f1ec6d --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_directory_async.py @@ -0,0 +1,632 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +import asyncio + +from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError + +from azure.storage.file.aio import ( + FileServiceClient, + StorageErrorCode, +) +from filetestcase import ( + FileTestCase, + record, + LogCaptured, + TestMode +) + + +# ------------------------------------------------------------------------------ + + +class StorageDirectoryTest(FileTestCase): + def setUp(self): + super(StorageDirectoryTest, self).setUp() + + url = self.get_file_url() + credential = self.get_shared_key_credential() + self.fsc = FileServiceClient(url, credential=credential) + self.share_name = self.get_resource_name('utshare') + + def tearDown(self): + if not self.is_playback(): + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(self.fsc.delete_share(self.share_name, delete_snapshots=True)) + except: + pass + + return super(StorageDirectoryTest, self).tearDown() + + # --Helpers----------------------------------------------------------------- + async def _setup(self): + if not self.is_playback(): + try: + await self.fsc.create_share(self.share_name) + except: + pass + + # --Test cases for directories ---------------------------------------------- + async def _test_create_directories_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + + # Act + created = await share_client.create_directory('dir1') + + # Assert + self.assertTrue(created) + + def test_create_directories_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_directories_async()) + + async def _test_create_directories_with_metadata_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + metadata = {'hello': 'world', 'number': '42'} + + # Act + directory = await share_client.create_directory('dir1', metadata=metadata) + + # Assert + props = await directory.get_directory_properties() + self.assertDictEqual(props.metadata, metadata) + + def test_create_directories_with_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_directories_with_metadata_async()) + + async def _test_create_directories_fail_on_exist_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + + # Act + created = await share_client.create_directory('dir1') + with self.assertRaises(ResourceExistsError): + await share_client.create_directory('dir1') + + # Assert + self.assertTrue(created) + + def test_create_directories_fail_on_exist_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_directories_fail_on_exist_async()) + + async def _test_create_subdirectories_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + created = await directory.create_subdirectory('dir2') + + # Assert + self.assertTrue(created) + self.assertEqual(created.directory_path, 'dir1/dir2') + + def test_create_subdirectories_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_subdirectories_async()) + + async def _test_create_subdirectories_with_metadata_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + metadata = {'hello': 'world', 'number': '42'} + + # Act + created = await directory.create_subdirectory('dir2', metadata=metadata) + + # Assert + self.assertTrue(created) + self.assertEqual(created.directory_path, 'dir1/dir2') + properties = await created.get_directory_properties() + self.assertEqual(properties.metadata, metadata) + + def test_create_subdirectories_with_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_subdirectories_with_metadata_async()) + + async def _test_create_file_in_directory_async(self): + # Arrange + await self._setup() + file_data = b'12345678' * 1024 + file_name = self.get_resource_name('file') + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + new_file = await directory.upload_file(file_name, file_data) + + # Assert + file_content = await new_file.download_file() + file_content = await file_content.content_as_bytes() + self.assertEqual(file_content, file_data) + + def test_create_file_in_directory_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_in_directory_async()) + + async def _test_delete_file_in_directory_async(self): + # Arrange + await self._setup() + file_name = self.get_resource_name('file') + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + new_file = await directory.upload_file(file_name, "hello world") + + # Act + deleted = await directory.delete_file(file_name) + + # Assert + self.assertIsNone(deleted) + with self.assertRaises(ResourceNotFoundError): + await new_file.get_file_properties() + + def test_delete_file_in_directory_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_file_in_directory_async()) + + async def _test_delete_subdirectories_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + await directory.create_subdirectory('dir2') + + # Act + deleted = await directory.delete_subdirectory('dir2') + + # Assert + self.assertIsNone(deleted) + subdir = directory.get_subdirectory_client('dir2') + with self.assertRaises(ResourceNotFoundError): + await subdir.get_directory_properties() + + def test_delete_subdirectories_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_subdirectories_async()) + + async def _test_get_directory_properties_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + props = await directory.get_directory_properties() + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + + def test_get_directory_properties_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_directory_properties_async()) + + async def _test_get_directory_properties_with_snapshot_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + metadata = {"test1": "foo", "test2": "bar"} + directory = await share_client.create_directory('dir1', metadata=metadata) + snapshot1 = await share_client.create_snapshot() + metadata2 = {"test100": "foo100", "test200": "bar200"} + await directory.set_directory_metadata(metadata2) + + # Act + share_client = self.fsc.get_share_client(self.share_name, snapshot=snapshot1) + snap_dir = share_client.get_directory_client('dir1') + props = await snap_dir.get_directory_properties() + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + self.assertDictEqual(metadata, props.metadata) + + def test_get_directory_properties_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_directory_properties_with_snapshot_async()) + + async def _test_get_directory_metadata_with_snapshot_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + metadata = {"test1": "foo", "test2": "bar"} + directory = await share_client.create_directory('dir1', metadata=metadata) + snapshot1 = await share_client.create_snapshot() + metadata2 = {"test100": "foo100", "test200": "bar200"} + await directory.set_directory_metadata(metadata2) + + # Act + share_client = self.fsc.get_share_client(self.share_name, snapshot=snapshot1) + snap_dir = share_client.get_directory_client('dir1') + snapshot_props = await snap_dir.get_directory_properties() + + # Assert + self.assertIsNotNone(snapshot_props.metadata) + self.assertDictEqual(metadata, snapshot_props.metadata) + + def test_get_directory_metadata_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_directory_metadata_with_snapshot_async()) + + async def _test_get_directory_properties_with_non_existing_directory_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = share_client.get_directory_client('dir1') + + # Act + with self.assertRaises(ResourceNotFoundError): + await directory.get_directory_properties() + + # Assert + + def test_get_directory_properties_with_non_existing_directory_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_directory_properties_with_non_existing_directory_async()) + + async def _test_directory_exists_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + exists = await directory.get_directory_properties() + + # Assert + self.assertTrue(exists) + + def test_directory_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_directory_exists_async()) + + async def _test_directory_not_exists_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = share_client.get_directory_client('dir1') + + # Act + with self.assertRaises(ResourceNotFoundError): + await directory.get_directory_properties() + + # Assert + + def test_directory_not_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_directory_not_exists_async()) + + async def _test_directory_parent_not_exists_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = share_client.get_directory_client('missing1/missing2') + + # Act + with self.assertRaises(ResourceNotFoundError) as e: + await directory.get_directory_properties() + + # Assert + self.assertEqual(e.exception.error_code, StorageErrorCode.parent_not_found) + + def test_directory_parent_not_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_directory_parent_not_exists_async()) + + async def _test_directory_exists_with_snapshot_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + snapshot = await share_client.create_snapshot() + await directory.delete_directory() + + # Act + share_client = self.fsc.get_share_client(self.share_name, snapshot=snapshot) + snap_dir = share_client.get_directory_client('dir1') + exists = await snap_dir.get_directory_properties() + + # Assert + self.assertTrue(exists) + + def test_directory_exists_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_directory_exists_with_snapshot_async()) + + async def _test_directory_not_exists_with_snapshot_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + directory = await share_client.create_directory('dir1') + + # Act + share_client = self.fsc.get_share_client(self.share_name, snapshot=snapshot) + snap_dir = share_client.get_directory_client('dir1') + + with self.assertRaises(ResourceNotFoundError): + await snap_dir.get_directory_properties() + + # Assert + + def test_directory_not_exists_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_directory_not_exists_with_snapshot_async()) + + async def _test_get_set_directory_metadata_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + metadata = {'hello': 'world', 'number': '43'} + + # Act + await directory.set_directory_metadata(metadata) + props = await directory.get_directory_properties() + + # Assert + self.assertDictEqual(props.metadata, metadata) + + def test_get_set_directory_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_set_directory_metadata_async()) + + async def _test_list_subdirectories_and_files_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + await asyncio.gather( + directory.create_subdirectory("subdir1"), + directory.create_subdirectory("subdir2"), + directory.create_subdirectory("subdir3"), + directory.upload_file("file1", "data1"), + directory.upload_file("file2", "data2"), + directory.upload_file("file3", "data3")) + + # Act + list_dir = [] + async for d in directory.list_directories_and_files(): + list_dir.append(d) + + # Assert + expected = [ + {'name': 'subdir1', 'is_directory': True}, + {'name': 'subdir2', 'is_directory': True}, + {'name': 'subdir3', 'is_directory': True}, + {'name': 'file1', 'is_directory': False, 'size': 5}, + {'name': 'file2', 'is_directory': False, 'size': 5}, + {'name': 'file3', 'is_directory': False, 'size': 5}, + ] + self.assertEqual(len(list_dir), 6) + self.assertEqual(list_dir, expected) + + def test_list_subdirectories_and_files_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_subdirectories_and_files_async()) + + async def _test_list_subdirectories_and_files_with_prefix_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + await asyncio.gather( + directory.create_subdirectory("subdir1"), + directory.create_subdirectory("subdir2"), + directory.create_subdirectory("subdir3"), + directory.upload_file("file1", "data1"), + directory.upload_file("file2", "data2"), + directory.upload_file("file3", "data3")) + + # Act + list_dir = [] + async for d in directory.list_directories_and_files(name_starts_with="sub"): + list_dir.append(d) + + # Assert + expected = [ + {'name': 'subdir1', 'is_directory': True}, + {'name': 'subdir2', 'is_directory': True}, + {'name': 'subdir3', 'is_directory': True}, + ] + self.assertEqual(len(list_dir), 3) + self.assertEqual(list_dir, expected) + + def test_list_subdirectories_and_files_with_prefix_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_subdirectories_and_files_with_prefix_async()) + + async def _test_list_subdirectories_and_files_with_snapshot_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + await asyncio.gather( + directory.create_subdirectory("subdir1"), + directory.create_subdirectory("subdir2"), + directory.upload_file("file1", "data1")) + + snapshot = await share_client.create_snapshot() + await asyncio.gather( + directory.create_subdirectory("subdir3"), + directory.upload_file("file2", "data2"), + directory.upload_file("file3", "data3")) + + share_client = self.fsc.get_share_client(self.share_name, snapshot=snapshot) + snapshot_dir = share_client.get_directory_client('dir1') + + # Act + list_dir = [] + async for d in snapshot_dir.list_directories_and_files(): + list_dir.append(d) + + # Assert + expected = [ + {'name': 'subdir1', 'is_directory': True}, + {'name': 'subdir2', 'is_directory': True}, + {'name': 'file1', 'is_directory': False, 'size': 5}, + ] + self.assertEqual(len(list_dir), 3) + self.assertEqual(list_dir, expected) + + def test_list_subdirectories_and_files_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_subdirectories_and_files_with_snapshot_async()) + + async def _test_list_nested_subdirectories_and_files_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + subdir = await directory.create_subdirectory("subdir1") + await subdir.create_subdirectory("subdir2") + await subdir.create_subdirectory("subdir3") + await asyncio.gather( + directory.upload_file("file1", "data1"), + subdir.upload_file("file2", "data2"), + subdir.upload_file("file3", "data3")) + + # Act + list_dir = [] + async for d in directory.list_directories_and_files(): + list_dir.append(d) + + # Assert + expected = [ + {'name': 'subdir1', 'is_directory': True}, + {'name': 'file1', 'is_directory': False, 'size': 5}, + ] + self.assertEqual(len(list_dir), 2) + self.assertEqual(list_dir, expected) + + def test_list_nested_subdirectories_and_files_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_nested_subdirectories_and_files_async()) + + async def _test_delete_directory_with_existing_share_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + deleted = await directory.delete_directory() + + # Assert + self.assertIsNone(deleted) + with self.assertRaises(ResourceNotFoundError): + await directory.get_directory_properties() + + def test_delete_directory_with_existing_share_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_directory_with_existing_share_async()) + + async def _test_delete_directory_with_non_existing_directory_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = share_client.get_directory_client('dir1') + + # Act + with self.assertRaises(ResourceNotFoundError): + await directory.delete_directory() + + # Assert + + def test_delete_directory_with_non_existing_directory_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_directory_with_non_existing_directory_async()) + + async def _test_get_directory_properties_server_encryption_async(self): + # Arrange + await self._setup() + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory('dir1') + + # Act + props = await directory.get_directory_properties() + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + + if self.is_file_encryption_enabled(): + self.assertTrue(props.server_encrypted) + else: + self.assertFalse(props.server_encrypted) + + def test_get_directory_properties_server_encryption_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_directory_properties_server_encryption_async()) + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-file/tests/test_file.py b/sdk/storage/azure-storage-file/tests/test_file.py index 834be61d1969..c457cff33573 100644 --- a/sdk/storage/azure-storage-file/tests/test_file.py +++ b/sdk/storage/azure-storage-file/tests/test_file.py @@ -8,6 +8,7 @@ import base64 import os import unittest +import time from datetime import datetime, timedelta import requests @@ -117,16 +118,18 @@ def _create_remote_file(self, file_data=None): remote_file.upload_file(file_data) return remote_file - def _wait_for_async_copy(self, share_name, dir_name, file_name): + def _wait_for_async_copy(self, share_name, file_path): count = 0 - file = self.fs.get_file_properties(share_name, dir_name, file_name) - while file.properties.copy.status != 'success': + share_client = self.fsc.get_share_client(share_name) + file_client = share_client.get_file_client(file_path) + properties = file_client.get_file_properties() + while properties.copy.status != 'success': count = count + 1 if count > 10: self.fail('Timed out waiting for async copy to complete.') self.sleep(6) - file = self.fs.get_file_properties(share_name, dir_name, file_name) - self.assertEqual(file.properties.copy.status, 'success') + properties = file_client.get_file_properties() + self.assertEqual(properties.copy.status, 'success') def assertFileEqual(self, file_client, expected_data): actual_data = file_client.download_file().content_as_bytes() @@ -655,12 +658,12 @@ def test_copy_file_with_existing_file(self): credential=self.settings.STORAGE_ACCOUNT_KEY) # Act - copy = file_client.copy_file_from_url(source_client.url) + copy = file_client.start_copy_from_url(source_client.url) # Assert self.assertIsNotNone(copy) - self.assertEqual(copy.status(), 'success') - self.assertIsNotNone(copy.copy_id()) + self.assertEqual(copy['copy_status'], 'success') + self.assertIsNotNone(copy['copy_id']) copy_file = file_client.download_file().content_as_bytes() self.assertEqual(copy_file, self.short_byte_data) @@ -679,7 +682,7 @@ def test_copy_file_async_private_file(self): file_path=target_file_name, credential=self.settings.STORAGE_ACCOUNT_KEY) with self.assertRaises(HttpResponseError) as e: - file_client.copy_file_from_url(source_file.url) + file_client.start_copy_from_url(source_file.url) # Assert self.assertEqual(e.exception.error_code, StorageErrorCode.cannot_verify_copy_source) @@ -703,13 +706,11 @@ def test_copy_file_async_private_file_with_sas(self): share=self.share_name, file_path=target_file_name, credential=self.settings.STORAGE_ACCOUNT_KEY) - copy_resp = file_client.copy_file_from_url(source_url) + copy_resp = file_client.start_copy_from_url(source_url) # Assert - status = copy_resp.status() - self.assertTrue(status in ['success', 'pending']) - if status == 'pending': - copy_resp.wait() + self.assertTrue(copy_resp['copy_status'] in ['success', 'pending']) + self._wait_for_async_copy(self.share_name, target_file_name) actual_data = file_client.download_file().content_as_bytes() self.assertEqual(actual_data, data) @@ -733,9 +734,9 @@ def test_abort_copy_file(self): share=self.share_name, file_path=target_file_name, credential=self.settings.STORAGE_ACCOUNT_KEY) - copy_resp = file_client.copy_file_from_url(source_url) - self.assertEqual(copy_resp.status(), 'pending') - copy_resp.abort() + copy_resp = file_client.start_copy_from_url(source_url) + self.assertEqual(copy_resp['copy_status'], 'pending') + file_client.abort_copy(copy_resp) # Assert target_file = file_client.download_file() @@ -754,13 +755,13 @@ def test_abort_copy_file_with_synchronous_copy_fails(self): share=self.share_name, file_path=target_file_name, credential=self.settings.STORAGE_ACCOUNT_KEY) - copy_resp = file_client.copy_file_from_url(source_file.url) + copy_resp = file_client.start_copy_from_url(source_file.url) with self.assertRaises(HttpResponseError): - copy_resp.abort() + file_client.abort_copy(copy_resp) # Assert - self.assertEqual(copy_resp.status(), 'success') + self.assertEqual(copy_resp['copy_status'], 'success') @record def test_unicode_get_file_unicode_name(self): @@ -942,7 +943,7 @@ def callback(response): self.assertFileEqual(file_client, data) self.assert_upload_progress( len(data), - self.fsc._config.data_settings.max_range_size, + self.fsc._config.max_range_size, progress, unknown_size=False) def test_create_file_from_stream(self): @@ -1027,7 +1028,7 @@ def callback(response): self.assertFileEqual(file_client, data[:file_size]) self.assert_upload_progress( len(data), - self.fsc._config.data_settings.max_range_size, + self.fsc._config.max_range_size, progress, unknown_size=False) def test_create_file_from_stream_truncated(self): @@ -1089,7 +1090,7 @@ def callback(response): self.assertFileEqual(file_client, data[:file_size]) self.assert_upload_progress( file_size, - self.fsc._config.data_settings.max_range_size, + self.fsc._config.max_range_size, progress, unknown_size=False) @record diff --git a/sdk/storage/azure-storage-file/tests/test_file_async.py b/sdk/storage/azure-storage-file/tests/test_file_async.py new file mode 100644 index 000000000000..83e4cdae1c19 --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_file_async.py @@ -0,0 +1,1746 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import base64 +import os +import unittest +import time +from datetime import datetime, timedelta +import asyncio + +import requests +import pytest + +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError + +from azure.storage.file.aio import ( + FileClient, + FileServiceClient, + ContentSettings, + FilePermissions, + AccessPolicy, + ResourceTypes, + AccountPermissions, + StorageErrorCode +) +from filetestcase import ( + FileTestCase, + TestMode, + record, +) + +# ------------------------------------------------------------------------------ +TEST_SHARE_PREFIX = 'share' +TEST_DIRECTORY_PREFIX = 'dir' +TEST_FILE_PREFIX = 'file' +INPUT_FILE_PATH = 'file_input.temp.dat' +OUTPUT_FILE_PATH = 'file_output.temp.dat' +LARGE_FILE_SIZE = 64 * 1024 + 5 + + +# ------------------------------------------------------------------------------ + +class StorageFileTestAsync(FileTestCase): + def setUp(self): + super(StorageFileTestAsync, self).setUp() + + url = self.get_file_url() + credential = self.get_shared_key_credential() + + # test chunking functionality by reducing the threshold + # for chunking and the size of each chunk, otherwise + # the tests would take too long to execute + self.fsc = FileServiceClient(url, credential=credential, max_range_size=4 * 1024) + self.share_name = self.get_resource_name('utshare') + self.short_byte_data = self.get_random_bytes(1024) + + remote_url = self.get_remote_file_url() + remote_credential = self.get_remote_shared_key_credential() + self.fsc2 = FileServiceClient(remote_url, credential=remote_credential) + self.remote_share_name = None + + def tearDown(self): + if not self.is_playback(): + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(self.fsc.delete_share(self.share_name, delete_snapshots=True)) + except: + pass + + if self.remote_share_name: + try: + loop.run_until_complete(self.fs2.delete_share(self.remote_share_name, delete_snapshots=True)) + except: + pass + + if os.path.isfile(INPUT_FILE_PATH): + try: + os.remove(INPUT_FILE_PATH) + except: + pass + + if os.path.isfile(OUTPUT_FILE_PATH): + try: + os.remove(OUTPUT_FILE_PATH) + except: + pass + + return super(StorageFileTestAsync, self).tearDown() + + # --Helpers----------------------------------------------------------------- + def _get_file_reference(self): + return self.get_resource_name(TEST_FILE_PREFIX) + + async def _setup_share(self, remote=False): + share_name = self.remote_share_name if remote else self.share_name + async with FileServiceClient( + self.get_file_url(), + credential=self.get_shared_key_credential(), + max_range_size=4 * 1024) as fsc: + if not self.is_playback(): + try: + await fsc.create_share(share_name) + except: + pass + + async def _create_file(self): + await self._setup_share() + file_name = self._get_file_reference() + share_client = self.fsc.get_share_client(self.share_name) + file_client = share_client.get_file_client(file_name) + await file_client.upload_file(self.short_byte_data) + return file_client + + async def _create_remote_share(self): + self.remote_share_name = self.get_resource_name('remoteshare') + remote_share = self.fsc2.get_share_client(self.remote_share_name) + await remote_share.create_share() + return remote_share + + async def _create_remote_file(self, file_data=None): + if not file_data: + file_data = b'12345678' * 1024 * 1024 + source_file_name = self._get_file_reference() + remote_share = self.fsc2.get_share_client(self.remote_share_name) + remote_file = remote_share.get_file_client(source_file_name) + await remote_file.upload_file(file_data) + return remote_file + + async def _wait_for_async_copy(self, share_name, file_path): + count = 0 + share_client = self.fsc.get_share_client(share_name) + file_client = share_client.get_file_client(file_path) + properties = await file_client.get_file_properties() + while properties.copy.status != 'success': + count = count + 1 + if count > 10: + self.fail('Timed out waiting for async copy to complete.') + self.sleep(6) + properties = await file_client.get_file_properties() + self.assertEqual(properties.copy.status, 'success') + + async def assertFileEqual(self, file_client, expected_data): + content = await file_client.download_file() + actual_data = await content.content_as_bytes() + self.assertEqual(actual_data, expected_data) + + class NonSeekableFile(object): + def __init__(self, wrapped_file): + self.wrapped_file = wrapped_file + + def write(self, data): + self.wrapped_file.write(data) + + def read(self, count): + return self.wrapped_file.read(count) + + # --Test cases for files ---------------------------------------------- + async def _test_make_file_url_async(self): + # Arrange + + share = self.fsc.get_share_client("vhds") + file_client = share.get_file_client("vhd_dir/my.vhd") + + # Act + res = file_client.url + + # Assert + self.assertEqual(res, 'https://' + self.settings.STORAGE_ACCOUNT_NAME + + '.file.core.windows.net/vhds/vhd_dir/my.vhd') + + def test_make_file_url_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_make_file_url_async()) + + async def _test_make_file_url_no_directory_async(self): + # Arrange + share = self.fsc.get_share_client("vhds") + file_client = share.get_file_client("my.vhd") + + # Act + res = file_client.url + + # Assert + self.assertEqual(res, 'https://' + self.settings.STORAGE_ACCOUNT_NAME + + '.file.core.windows.net/vhds/my.vhd') + + def test_make_file_url_no_directory_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_make_file_url_no_directory_async()) + + async def _test_make_file_url_with_protocol(self): + # Arrange + url = self.get_file_url().replace('https', 'http') + fsc = FileServiceClient(url, credential=self.settings.STORAGE_ACCOUNT_KEY) + share = fsc.get_share_client("vhds") + file_client = share.get_file_client("vhd_dir/my.vhd") + + # Act + res = file_client.url + + # Assert + self.assertEqual(res, 'http://' + self.settings.STORAGE_ACCOUNT_NAME + + '.file.core.windows.net/vhds/vhd_dir/my.vhd') + + def test_make_file_url_with_protocol(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_make_file_url_with_protocol()) + + async def _test_make_file_url_with_sas(self): + # Arrange + sas = '?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D' + file_client = FileClient( + self.get_file_url(), + share="vhds", + file_path="vhd_dir/my.vhd", + credential=sas + ) + + # Act + res = file_client.url + + # Assert + self.assertEqual(res, 'https://' + self.settings.STORAGE_ACCOUNT_NAME + + '.file.core.windows.net/vhds/vhd_dir/my.vhd{}'.format(sas)) + + def test_make_file_url_with_sas(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_make_file_url_with_sas()) + + async def _test_create_file_async(self): + # Arrange + await self._setup_share() + file_name = self._get_file_reference() + async with FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) as file_client: + + # Act + resp = await file_client.create_file(1024) + + # Assert + props = await file_client.get_file_properties() + self.assertIsNotNone(props) + self.assertEqual(props.etag, resp['etag']) + self.assertEqual(props.last_modified, resp['last_modified']) + + def test_create_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_async()) + + async def _test_create_file_with_metadata_async(self): + # Arrange + await self._setup_share() + metadata = {'hello': 'world', 'number': '42'} + file_name = self._get_file_reference() + async with FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) as file_client: + + # Act + resp = await file_client.create_file(1024, metadata=metadata) + + # Assert + props = await file_client.get_file_properties() + self.assertIsNotNone(props) + self.assertEqual(props.etag, resp['etag']) + self.assertEqual(props.last_modified, resp['last_modified']) + self.assertDictEqual(props.metadata, metadata) + + def test_create_file_with_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_with_metadata_async()) + + async def _test_file_exists_async(self): + # Arrange + file_client = await self._create_file() + + # Act + exists = await file_client.get_file_properties() + + # Assert + self.assertTrue(exists) + + def test_file_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_exists_async()) + + async def _test_file_not_exists_async(self): + # Arrange + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path="missingdir/" + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + with self.assertRaises(ResourceNotFoundError): + await file_client.get_file_properties() + + # Assert + + def test_file_not_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_not_exists_async()) + + async def _test_file_exists_with_snapshot_async(self): + # Arrange + file_client = await self._create_file() + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + await file_client.delete_file() + + # Act + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + props = await snapshot_client.get_file_properties() + + # Assert + self.assertTrue(props) + + def test_file_exists_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_exists_with_snapshot_async()) + + async def _test_file_not_exists_with_snapshot_async(self): + # Arrange + await self._setup_share() + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + + file_client = await self._create_file() + + # Act + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Assert + with self.assertRaises(ResourceNotFoundError): + await snapshot_client.get_file_properties() + + def test_file_not_exists_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_not_exists_with_snapshot_async()) + + async def _test_resize_file_async(self): + # Arrange + file_client = await self._create_file() + + # Act + await file_client.resize_file(5) + + # Assert + props = await file_client.get_file_properties() + self.assertEqual(props.size, 5) + + def test_resize_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_resize_file_async()) + + async def _test_set_file_properties_async(self): + # Arrange + file_client = await self._create_file() + + # Act + content_settings = ContentSettings( + content_language='spanish', + content_disposition='inline') + resp = await file_client.set_http_headers(content_settings=content_settings) + + # Assert + properties = await file_client.get_file_properties() + self.assertEqual(properties.content_settings.content_language, content_settings.content_language) + self.assertEqual(properties.content_settings.content_disposition, content_settings.content_disposition) + + def test_set_file_properties_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_file_properties_async()) + + async def _test_get_file_properties_async(self): + # Arrange + file_client = await self._create_file() + + # Act + properties = await file_client.get_file_properties() + + # Assert + self.assertIsNotNone(properties) + self.assertEqual(properties.size, len(self.short_byte_data)) + + def test_get_file_properties_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_properties_async()) + + async def _test_get_file_properties_with_snapshot_async(self): + # Arrange + file_client = await self._create_file() + metadata = {"test1": "foo", "test2": "bar"} + await file_client.set_file_metadata(metadata) + + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + + metadata2 = {"test100": "foo100", "test200": "bar200"} + await file_client.set_file_metadata(metadata2) + + # Act + file_props = await file_client.get_file_properties() + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + snapshot_props = await snapshot_client.get_file_properties() + + # Assert + self.assertIsNotNone(file_props) + self.assertIsNotNone(snapshot_props) + self.assertEqual(snapshot_props.snapshot, snapshot_client.snapshot) + self.assertEqual(file_props.size, snapshot_props.size) + self.assertDictEqual(metadata, snapshot_props.metadata) + + def test_get_file_properties_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_properties_with_snapshot_async()) + + async def _test_get_file_metadata_with_snapshot_async(self): + # Arrange + file_client = await self._create_file() + metadata = {"test1": "foo", "test2": "bar"} + await file_client.set_file_metadata(metadata) + + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + metadata2 = {"test100": "foo100", "test200": "bar200"} + await file_client.set_file_metadata(metadata2) + + # Act + file_metadata = await file_client.get_file_properties() + file_snapshot_metadata = await snapshot_client.get_file_properties() + + # Assert + self.assertDictEqual(metadata2, file_metadata.metadata) + self.assertDictEqual(metadata, file_snapshot_metadata.metadata) + + def test_get_file_metadata_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_metadata_with_snapshot_async()) + + async def _test_get_file_properties_with_non_existing_file_async(self): + # Arrange + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + with self.assertRaises(ResourceNotFoundError): + await file_client.get_file_properties() + + # Assert + + def test_get_file_properties_with_non_existing_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_properties_with_non_existing_file_async()) + + async def _test_get_file_metadata_async(self): + # Arrange + file_client = await self._create_file() + + # Act + md = await file_client.get_file_properties() + + # Assert + self.assertIsNotNone(md.metadata) + self.assertEqual(0, len(md.metadata)) + + def test_get_file_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_metadata_async()) + + async def _test_set_file_metadata_with_upper_case_async(self): + # Arrange + metadata = {'hello': 'world', 'number': '42', 'UP': 'UPval'} + file_client = await self._create_file() + + # Act + await file_client.set_file_metadata(metadata) + + # Assert + props = await file_client.get_file_properties() + md = props.metadata + self.assertEqual(3, len(md)) + self.assertEqual(md['hello'], 'world') + self.assertEqual(md['number'], '42') + self.assertEqual(md['UP'], 'UPval') + self.assertFalse('up' in md) + + def test_set_file_metadata_with_upper_case_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_file_metadata_with_upper_case_async()) + + async def _test_delete_file_with_existing_file_async(self): + # Arrange + file_client = await self._create_file() + + # Act + await file_client.delete_file() + + # Assert + with self.assertRaises(ResourceNotFoundError): + await file_client.get_file_properties() + + def test_delete_file_with_existing_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_file_with_existing_file_async()) + + async def _test_delete_file_with_non_existing_file_async(self): + # Arrange + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + with self.assertRaises(ResourceNotFoundError): + await file_client.delete_file() + + # Assert + + def test_delete_file_with_non_existing_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_file_with_non_existing_file_async()) + + async def _test_update_range_async(self): + # Arrange + file_client = await self._create_file() + + # Act + data = b'abcdefghijklmnop' * 32 + await file_client.upload_range(data, 0, 511) + + # Assert + content = await file_client.download_file() + content = await content.content_as_bytes() + self.assertEqual(data, content[:512]) + self.assertEqual(self.short_byte_data[512:], content[512:]) + + def test_update_range_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_update_range_async()) + + async def _test_update_range_with_md5_async(self): + # Arrange + file_client = await self._create_file() + + # Act + data = b'abcdefghijklmnop' * 32 + await file_client.upload_range(data, 0, 511, validate_content=True) + + # Assert + + def test_update_range_with_md5_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_update_range_with_md5_async()) + + async def _test_clear_range_async(self): + # Arrange + file_client = await self._create_file() + + # Act + resp = await file_client.clear_range(0, 511) + + # Assert + content = await file_client.download_file() + content = await content.content_as_bytes() + self.assertEqual(b'\x00' * 512, content[:512]) + self.assertEqual(self.short_byte_data[512:], content[512:]) + + def test_clear_range_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_clear_range_async()) + + async def _test_update_file_unicode_async(self): + # Arrange + file_client = await self._create_file() + + # Act + data = u'abcdefghijklmnop' * 32 + await file_client.upload_range(data, 0, 511) + + encoded = data.encode('utf-8') + + # Assert + content = await file_client.download_file() + content = await content.content_as_bytes() + self.assertEqual(encoded, content[:512]) + self.assertEqual(self.short_byte_data[512:], content[512:]) + + # Assert + + def test_update_file_unicode_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_update_file_unicode_async()) + + async def _test_list_ranges_none_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.create_file(1024) + + # Act + ranges = await file_client.get_ranges() + + # Assert + self.assertIsNotNone(ranges) + self.assertEqual(len(ranges), 0) + + def test_list_ranges_none_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_ranges_none_async()) + + async def _test_list_ranges_2_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.create_file(2048) + + data = b'abcdefghijklmnop' * 32 + resp1 = await file_client.upload_range(data, 0, 511) + resp2 = await file_client.upload_range(data, 1024, 1535) + + # Act + ranges = await file_client.get_ranges() + + # Assert + self.assertIsNotNone(ranges) + self.assertEqual(len(ranges), 2) + self.assertEqual(ranges[0]['start'], 0) + self.assertEqual(ranges[0]['end'], 511) + self.assertEqual(ranges[1]['start'], 1024) + self.assertEqual(ranges[1]['end'], 1535) + + def test_list_ranges_2_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_ranges_2_async()) + + async def _test_list_ranges_none_from_snapshot_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.create_file(1024) + + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + await file_client.delete_file() + + # Act + ranges = await snapshot_client.get_ranges() + + # Assert + self.assertIsNotNone(ranges) + self.assertEqual(len(ranges), 0) + + def test_list_ranges_none_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_ranges_none_from_snapshot_async()) + + async def _test_list_ranges_2_from_snapshot_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.create_file(2048) + data = b'abcdefghijklmnop' * 32 + resp1 = await file_client.upload_range(data, 0, 511) + resp2 = await file_client.upload_range(data, 1024, 1535) + + share_client = self.fsc.get_share_client(self.share_name) + snapshot = await share_client.create_snapshot() + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + await file_client.delete_file() + + # Act + ranges = await snapshot_client.get_ranges() + + # Assert + self.assertIsNotNone(ranges) + self.assertEqual(len(ranges), 2) + self.assertEqual(ranges[0]['start'], 0) + self.assertEqual(ranges[0]['end'], 511) + self.assertEqual(ranges[1]['start'], 1024) + self.assertEqual(ranges[1]['end'], 1535) + + def test_list_ranges_2_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_ranges_2_from_snapshot_async()) + + async def _test_copy_file_with_existing_file_async(self): + # Arrange + source_client = await self._create_file() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path='file1copy', + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + copy = await file_client.start_copy_from_url(source_client.url) + + # Assert + self.assertIsNotNone(copy) + self.assertEqual(copy['copy_status'], 'success') + self.assertIsNotNone(copy['copy_id']) + + copy_file = await file_client.download_file() + content = await copy_file.content_as_bytes() + self.assertEqual(content, self.short_byte_data) + + def test_copy_file_with_existing_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_copy_file_with_existing_file_async()) + + async def _test_copy_file_async_private_file_async(self): + # Arrange + await self._create_remote_share() + source_file = await self._create_remote_file() + + # Act + target_file_name = 'targetfile' + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=target_file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + with self.assertRaises(HttpResponseError) as e: + await file_client.start_copy_from_url(source_file.url) + + # Assert + self.assertEqual(e.exception.error_code, StorageErrorCode.cannot_verify_copy_source) + + def test_copy_file_async_private_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_copy_file_async_private_file_async()) + + async def _test_copy_file_async_private_file_with_sas_async(self): + # Arrange + data = b'12345678' * 1024 * 1024 + await self._create_remote_share() + source_file = await self._create_remote_file(file_data=data) + sas_token = source_file.generate_shared_access_signature( + permission=FilePermissions.READ, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + source_url = source_file.url + '?' + sas_token + + # Act + target_file_name = 'targetfile' + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=target_file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + copy_resp = await file_client.start_copy_from_url(source_url) + + # Assert + self.assertTrue(copy_resp['copy_status'] in ['success', 'pending']) + await self._wait_for_async_copy(self.share_name, target_file_name) + + content = await file_client.download_file() + actual_data = await content.content_as_bytes() + self.assertEqual(actual_data, data) + + def test_copy_file_async_private_file_with_sas_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_copy_file_async_private_file_with_sas_async()) + + async def _test_abort_copy_file_async(self): + # Arrange + data = b'12345678' * 1024 * 1024 + await self._create_remote_share() + source_file = await self._create_remote_file(file_data=data) + sas_token = source_file.generate_shared_access_signature( + permission=FilePermissions.READ, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + source_url = source_file.url + '?' + sas_token + + # Act + target_file_name = 'targetfile' + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=target_file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + copy_resp = await file_client.start_copy_from_url(source_url) + self.assertEqual(copy_resp['copy_status'], 'pending') + await file_client.abort_copy(copy_resp) + + # Assert + target_file = await file_client.download_file() + content = await target_file.content_as_bytes() + self.assertEqual(content, b'') + self.assertEqual(target_file.properties.copy.status, 'aborted') + + def test_abort_copy_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_abort_copy_file_async()) + + async def _test_abort_copy_file_with_synchronous_copy_fails_async(self): + # Arrange + source_file = await self._create_file() + + # Act + target_file_name = 'targetfile' + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=target_file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + copy_resp = await file_client.start_copy_from_url(source_file.url) + + with self.assertRaises(HttpResponseError): + await file_client.abort_copy(copy_resp) + + # Assert + self.assertEqual(copy_resp['copy_status'], 'success') + + def test_abort_copy_file_with_synchronous_copy_fails_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_abort_copy_file_with_synchronous_copy_fails_async()) + + async def _test_unicode_get_file_unicode_name_async(self): + # Arrange + file_name = '啊齄丂狛狜' + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.upload_file(b'hello world') + + # Act + content = await file_client.download_file() + content = await content.content_as_bytes() + + # Assert + self.assertEqual(content, b'hello world') + + def test_unicode_get_file_unicode_name_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_unicode_get_file_unicode_name_async()) + + async def _test_file_unicode_data_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + data = u'hello world啊齄丂狛狜'.encode('utf-8') + await file_client.upload_file(data) + + # Assert + content = await file_client.download_file() + content = await content.content_as_bytes() + self.assertEqual(content, data) + + def test_file_unicode_data_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_unicode_data_async()) + + async def _test_unicode_get_file_binary_data_async(self): + # Arrange + base64_data = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==' + binary_data = base64.b64decode(base64_data) + await self._setup_share() + + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.upload_file(binary_data) + + # Act + content = await file_client.download_file() + content = await content.content_as_bytes() + + # Assert + self.assertEqual(content, binary_data) + + def test_unicode_get_file_binary_data_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_unicode_get_file_binary_data_async()) + + async def _test_create_file_from_bytes_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup_share() + file_name = self._get_file_reference() + data = self.get_random_bytes(LARGE_FILE_SIZE) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + progress = [] + def callback(response): + current = response.context['upload_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + await file_client.upload_file(data, max_connections=2, raw_response_hook=callback) + + # Assert + await self.assertFileEqual(file_client, data) + + def test_create_file_from_bytes_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_bytes_with_progress_async()) + + async def _test_create_file_from_bytes_with_index_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + index = 1024 + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + await file_client.upload_file(data[index:], max_connections=2) + + # Assert + await self.assertFileEqual(file_client, data[1024:]) + + def test_create_file_from_bytes_with_index_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_bytes_with_index_async()) + + async def _test_create_file_from_bytes_with_index_and_count_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + index = 512 + count = 1024 + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + await file_client.upload_file(data[index:], length=count, max_connections=2) + + # Assert + await self.assertFileEqual(file_client, data[index:index + count]) + + def test_create_file_from_bytes_with_index_and_count_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_bytes_with_index_and_count_async()) + + async def _test_create_file_from_path_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, max_connections=2) + + # Assert + await self.assertFileEqual(file_client, data) + + def test_create_file_from_path_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_path_async()) + + async def _test_create_file_from_path_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + progress = [] + def callback(response): + current = response.context['upload_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, max_connections=2, raw_response_hook=callback) + + # Assert + await self.assertFileEqual(file_client, data) + self.assert_upload_progress( + len(data), + self.fsc._config.max_range_size, + progress, unknown_size=False) + + def test_create_file_from_path_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_path_with_progress_async()) + + async def _test_create_file_from_stream_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + file_size = len(data) + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, max_connections=2) + + # Assert + await self.assertFileEqual(file_client, data[:file_size]) + + def test_create_file_from_stream_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_stream_async()) + + async def _test_create_file_from_stream_non_seekable_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + + # Act + file_size = len(data) + with open(INPUT_FILE_PATH, 'rb') as stream: + non_seekable_file = StorageFileTestAsync.NonSeekableFile(stream) + await file_client.upload_file(non_seekable_file, length=file_size, max_connections=1) + + # Assert + await self.assertFileEqual(file_client, data[:file_size]) + + def test_create_file_from_stream_non_seekable_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_stream_non_seekable_async()) + + async def _test_create_file_from_stream_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + progress = [] + def callback(response): + current = response.context['upload_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + file_size = len(data) + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, max_connections=2, raw_response_hook=callback) + + # Assert + await self.assertFileEqual(file_client, data[:file_size]) + self.assert_upload_progress( + len(data), + self.fsc._config.max_range_size, + progress, unknown_size=False) + + def test_create_file_from_stream_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_stream_with_progress_async()) + + async def _test_create_file_from_stream_truncated_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + file_size = len(data) - 512 + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, length=file_size, max_connections=4) + + # Assert + await self.assertFileEqual(file_client, data[:file_size]) + + def test_create_file_from_stream_truncated_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_stream_truncated_async()) + + async def _test_create_file_from_stream_with_progress_truncated_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + with open(INPUT_FILE_PATH, 'wb') as stream: + stream.write(data) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + progress = [] + def callback(response): + current = response.context['upload_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + file_size = len(data) - 5 + with open(INPUT_FILE_PATH, 'rb') as stream: + await file_client.upload_file(stream, length=file_size, max_connections=2, raw_response_hook=callback) + + + # Assert + await self.assertFileEqual(file_client, data[:file_size]) + self.assert_upload_progress( + file_size, + self.fsc._config.max_range_size, + progress, unknown_size=False) + + def test_create_file_from_stream_with_progress_truncated_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_stream_with_progress_truncated_async()) + + async def _test_create_file_from_text_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + text = u'hello 啊齄丂狛狜 world' + data = text.encode('utf-8') + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + await file_client.upload_file(text) + + # Assert + await self.assertFileEqual(file_client, data) + + def test_create_file_from_text_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_text_async()) + + async def _test_create_file_from_text_with_encoding_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + text = u'hello 啊齄丂狛狜 world' + data = text.encode('utf-16') + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + await file_client.upload_file(text, encoding='UTF-16') + + # Assert + await self.assertFileEqual(file_client, data) + + def test_create_file_from_text_with_encoding_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_text_with_encoding_async()) + + async def _test_create_file_from_text_chunked_upload_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_text_data(LARGE_FILE_SIZE) + encoded_data = data.encode('utf-8') + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + await file_client.upload_file(data) + + # Assert + await self.assertFileEqual(file_client, encoded_data) + + def test_create_file_from_text_chunked_upload_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_from_text_chunked_upload_async()) + + async def _test_create_file_with_md5_small_async(self): + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(512) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + await file_client.upload_file(data, validate_content=True) + + # Assert + + def test_create_file_with_md5_small_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_with_md5_small_async()) + + async def _test_create_file_with_md5_large_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = self._get_file_reference() + await self._setup_share() + data = self.get_random_bytes(LARGE_FILE_SIZE) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_range_size=4 * 1024) + + # Act + await file_client.upload_file(data, validate_content=True, max_connections=2) + + # Assert + + def test_create_file_with_md5_large_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_file_with_md5_large_async()) + + # --Test cases for sas & acl ------------------------------------------------ + async def _test_sas_access_file_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = await self._create_file() + token = file_client.generate_shared_access_signature( + permission=FilePermissions.READ, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + + # Act + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + credential=token) + content = await file_client.download_file() + content = await content.content_as_bytes() + + # Assert + self.assertEqual(self.short_byte_data, content) + + def test_sas_access_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_sas_access_file_async()) + + async def _test_sas_signed_identifier_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = await self._create_file() + share_client = self.fsc.get_share_client(self.share_name) + + access_policy = AccessPolicy() + access_policy.start = datetime.utcnow() - timedelta(hours=1) + access_policy.expiry = datetime.utcnow() + timedelta(hours=1) + access_policy.permission = FilePermissions.READ + identifiers = {'testid': access_policy} + await share_client.set_share_access_policy(identifiers) + + token = file_client.generate_shared_access_signature(policy_id='testid') + + # Act + sas_file = FileClient( + file_client.url, + credential=token) + + content = await file_client.download_file() + content = await content.content_as_bytes() + + # Assert + self.assertEqual(self.short_byte_data, content) + + def test_sas_signed_identifier_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_sas_signed_identifier_async()) + + async def _test_account_sas_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = await self._create_file() + token = self.fsc.generate_shared_access_signature( + ResourceTypes.OBJECT, + AccountPermissions.READ, + datetime.utcnow() + timedelta(hours=1), + ) + + # Act + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + credential=token) + + response = requests.get(file_client.url) + + # Assert + self.assertTrue(response.ok) + self.assertEqual(self.short_byte_data, response.content) + + def test_account_sas_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_account_sas_async()) + + async def _test_shared_read_access_file_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = await self._create_file() + token = file_client.generate_shared_access_signature( + permission=FilePermissions.READ, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + + # Act + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + credential=token) + response = requests.get(file_client.url) + + # Assert + self.assertTrue(response.ok) + self.assertEqual(self.short_byte_data, response.content) + + def test_shared_read_access_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_shared_read_access_file_async()) + + async def _test_shared_read_access_file_with_content_query_params_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = await self._create_file() + token = file_client.generate_shared_access_signature( + permission=FilePermissions.READ, + expiry=datetime.utcnow() + timedelta(hours=1), + cache_control='no-cache', + content_disposition='inline', + content_encoding='utf-8', + content_language='fr', + content_type='text', + ) + + # Act + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client.file_name, + credential=token) + response = requests.get(file_client.url) + + # Assert + self.assertEqual(self.short_byte_data, response.content) + self.assertEqual(response.headers['cache-control'], 'no-cache') + self.assertEqual(response.headers['content-disposition'], 'inline') + self.assertEqual(response.headers['content-encoding'], 'utf-8') + self.assertEqual(response.headers['content-language'], 'fr') + self.assertEqual(response.headers['content-type'], 'text') + + def test_shared_read_access_file_with_content_query_params_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_shared_read_access_file_with_content_query_params_async()) + + async def _test_shared_write_access_file_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + updated_data = b'updated file data' + file_client_admin = await self._create_file() + token = file_client_admin.generate_shared_access_signature( + permission=FilePermissions.WRITE, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client_admin.file_name, + credential=token) + + # Act + headers = {'x-ms-range': 'bytes=0-16', 'x-ms-write': 'update'} + response = requests.put(file_client.url + '&comp=range', headers=headers, data=updated_data) + + # Assert + self.assertTrue(response.ok) + file_content = await file_client_admin.download_file() + file_content = await file_content.content_as_bytes() + self.assertEqual(updated_data, file_content[:len(updated_data)]) + + def test_shared_write_access_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_shared_write_access_file_async()) + + async def _test_shared_delete_access_file_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client_admin = await self._create_file() + token = file_client_admin.generate_shared_access_signature( + permission=FilePermissions.DELETE, + expiry=datetime.utcnow() + timedelta(hours=1), + ) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=file_client_admin.file_name, + credential=token) + + # Act + response = requests.delete(file_client.url) + + # Assert + self.assertTrue(response.ok) + with self.assertRaises(ResourceNotFoundError): + await file_client_admin.download_file() + + def test_shared_delete_access_file_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_shared_delete_access_file_async()) + + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-file/tests/test_file_client_async.py b/sdk/storage/azure-storage-file/tests/test_file_client_async.py new file mode 100644 index 000000000000..69b620aff52f --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_file_client_async.py @@ -0,0 +1,321 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +import platform +import asyncio + +from azure.storage.file.aio import ( + FileServiceClient, + ShareClient, + DirectoryClient, + FileClient) + +from filetestcase import ( + TestMode, + FileTestCase, + record, +) +#from azure.storage.common import TokenCredential + +# ------------------------------------------------------------------------------ +SERVICES = { + FileServiceClient: 'file', + ShareClient: 'file', + DirectoryClient: 'file', + FileClient: 'file', +} + +_CONNECTION_ENDPOINTS = {'file': 'FileEndpoint'} + +_CONNECTION_ENDPOINTS_SECONDARY = {'file': 'FileSecondaryEndpoint'} + +class StorageFileClientTest(FileTestCase): + def setUp(self): + super(StorageFileClientTest, self).setUp() + self.account_name = self.settings.STORAGE_ACCOUNT_NAME + self.account_key = self.settings.STORAGE_ACCOUNT_KEY + self.sas_token = '?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D' + self.token_credential = self.generate_oauth_token() + + # --Helpers----------------------------------------------------------------- + def validate_standard_account_endpoints(self, service, service_type, protocol='https'): + self.assertIsNotNone(service) + self.assertEqual(service.credential.account_name, self.account_name) + self.assertEqual(service.credential.account_key, self.account_key) + self.assertTrue(service.primary_endpoint.startswith('{}://{}.{}.core.windows.net/'.format( + protocol, self.account_name, service_type))) + self.assertTrue(service.secondary_endpoint.startswith('{}://{}-secondary.{}.core.windows.net/'.format( + protocol, self.account_name, service_type))) + + # --Direct Parameters Test Cases -------------------------------------------- + def test_create_service_with_key_async(self): + # Arrange + + for client, url in SERVICES.items(): + # Act + service = client( + self.get_file_url(), credential=self.account_key, + share='foo', directory_path='bar', file_path='baz') + + # Assert + self.validate_standard_account_endpoints(service, url) + self.assertEqual(service.scheme, 'https') + + def test_create_service_with_sas_async(self): + # Arrange + + for service_type in SERVICES: + # Act + service = service_type( + self.get_file_url(), credential=self.sas_token, + share='foo', directory_path='bar', file_path='baz') + + # Assert + self.assertIsNotNone(service) + self.assertIsNone(service.credential) + self.assertTrue(service.url.endswith(self.sas_token)) + + def test_create_service_with_token_async(self): + for service_type in SERVICES: + # Act + # token credential is not available for FileService + with self.assertRaises(ValueError): + service_type(self.get_file_url(), credential=self.token_credential, + share='foo', directory_path='bar', file_path='baz') + + def test_create_service_china_async(self): + # Arrange + url = self.get_file_url().replace('core.windows.net', 'core.chinacloudapi.cn') + for service_type in SERVICES.items(): + # Act + service = service_type[0]( + url, credential=self.account_key, + share='foo', directory_path='bar', file_path='baz') + + # Assert + self.assertIsNotNone(service) + self.assertEqual(service.credential.account_name, self.account_name) + self.assertEqual(service.credential.account_key, self.account_key) + self.assertEqual(service.primary_hostname, '{}.{}.core.chinacloudapi.cn'.format( + self.account_name, service_type[1])) + self.assertEqual(service.secondary_hostname, + '{}-secondary.{}.core.chinacloudapi.cn'.format(self.account_name, service_type[1])) + + def test_create_service_protocol_async(self): + # Arrange + url = self.get_file_url().replace('https', 'http') + for service_type in SERVICES.items(): + # Act + service = service_type[0]( + url, credential=self.account_key, share='foo', directory_path='bar', file_path='baz') + + # Assert + self.validate_standard_account_endpoints(service, service_type[1], protocol='http') + self.assertEqual(service.scheme, 'http') + + + def test_create_service_empty_key_async(self): + # Arrange + for service_type in SERVICES: + # Act + # Passing an empty key to create account should fail. + with self.assertRaises(ValueError) as e: + service_type( + self.get_file_url(), share='foo', directory_path='bar', file_path='baz') + + self.assertEqual( + str(e.exception), + 'You need to provide either an account key or SAS token when creating a storage service.') + + def test_create_service_missing_arguments_async(self): + # Arrange + + for service_type in SERVICES: + # Act + with self.assertRaises(ValueError): + service = service_type(None) + + def test_create_service_with_socket_timeout_async(self): + # Arrange + + for service_type in SERVICES.items(): + # Act + default_service = service_type[0]( + self.get_file_url(), credential=self.account_key, + share='foo', directory_path='bar', file_path='baz') + service = service_type[0]( + self.get_file_url(), credential=self.account_key, connection_timeout=22, + share='foo', directory_path='bar', file_path='baz') + + # Assert + self.validate_standard_account_endpoints(service, service_type[1]) + self.assertEqual(service._config.connection.timeout, 22) + self.assertTrue(default_service._config.connection.timeout in [20, (20, 2000)]) + + # --Connection String Test Cases -------------------------------------------- + + def test_create_service_with_connection_string_key_async(self): + # Arrange + conn_string = 'AccountName={};AccountKey={};'.format(self.account_name, self.account_key) + + for service_type in SERVICES.items(): + # Act + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + # Assert + self.validate_standard_account_endpoints(service, service_type[1]) + self.assertEqual(service.scheme, 'https') + + def test_create_service_with_connection_string_sas_async(self): + # Arrange + conn_string = 'AccountName={};SharedAccessSignature={};'.format(self.account_name, self.sas_token) + + for service_type in SERVICES.items(): + # Act + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + # Assert + self.assertIsNotNone(service) + self.assertIsNone(service.credential) + self.assertTrue(service.url.endswith(self.sas_token)) + + def test_create_service_with_connection_string_endpoint_protocol_async(self): + # Arrange + conn_string = 'AccountName={};AccountKey={};DefaultEndpointsProtocol=http;EndpointSuffix=core.chinacloudapi.cn;'.format( + self.account_name, self.account_key) + + for service_type in SERVICES.items(): + # Act + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + # Assert + self.assertIsNotNone(service) + self.assertEqual(service.credential.account_name, self.account_name) + self.assertEqual(service.credential.account_key, self.account_key) + self.assertEqual(service.primary_hostname, '{}.{}.core.chinacloudapi.cn'.format(self.account_name, service_type[1])) + self.assertEqual(service.secondary_hostname, + '{}-secondary.{}.core.chinacloudapi.cn'.format(self.account_name, service_type[1])) + self.assertEqual(service.scheme, 'http') + + def test_create_service_with_connection_string_emulated_async(self): + # Arrange + for service_type in SERVICES.items(): + conn_string = 'UseDevelopmentStorage=true;'.format(self.account_name, self.account_key) + + # Act + with self.assertRaises(ValueError): + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + def test_create_service_with_connection_string_fails_if_secondary_without_primary_async(self): + for service_type in SERVICES.items(): + # Arrange + conn_string = 'AccountName={};AccountKey={};{}=www.mydomain.com;'.format( + self.account_name, self.account_key, _CONNECTION_ENDPOINTS_SECONDARY.get(service_type[1])) + + # Act + + # Fails if primary excluded + with self.assertRaises(ValueError): + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + def test_create_service_with_connection_string_succeeds_if_secondary_with_primary_async(self): + for service_type in SERVICES.items(): + # Arrange + conn_string = 'AccountName={};AccountKey={};{}=www.mydomain.com;{}=www-sec.mydomain.com;'.format( + self.account_name, self.account_key, + _CONNECTION_ENDPOINTS.get(service_type[1]), + _CONNECTION_ENDPOINTS_SECONDARY.get(service_type[1])) + + # Act + service = service_type[0].from_connection_string( + conn_string, share='foo', directory_path='bar', file_path='baz') + + # Assert + self.assertIsNotNone(service) + self.assertEqual(service.credential.account_name, self.account_name) + self.assertEqual(service.credential.account_key, self.account_key) + self.assertEqual(service.primary_hostname, 'www.mydomain.com') + self.assertEqual(service.secondary_hostname, 'www-sec.mydomain.com') + + async def _test_user_agent_default_async(self): + service = FileServiceClient(self.get_file_url(), credential=self.account_key) + + def callback(response): + self.assertTrue('User-Agent' in response.http_request.headers) + self.assertEqual( + response.http_request.headers['User-Agent'], + "azsdk-python-storage-file/12.0.0b1 Python/{} ({})".format( + platform.python_version(), + platform.platform())) + + await service.get_service_properties(raw_response_hook=callback) + + def test_user_agent_default_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_user_agent_default_async()) + + async def _test_user_agent_custom_async(self): + custom_app = "TestApp/v1.0" + service = FileServiceClient( + self.get_file_url(), credential=self.account_key, user_agent=custom_app) + + def callback(response): + self.assertTrue('User-Agent' in response.http_request.headers) + self.assertEqual( + response.http_request.headers['User-Agent'], + "TestApp/v1.0 azsdk-python-storage-file/12.0.0b1 Python/{} ({})".format( + platform.python_version(), + platform.platform())) + + await service.get_service_properties(raw_response_hook=callback) + + def callback(response): + self.assertTrue('User-Agent' in response.http_request.headers) + self.assertEqual( + response.http_request.headers['User-Agent'], + "TestApp/v2.0 azsdk-python-storage-file/12.0.0b1 Python/{} ({})".format( + platform.python_version(), + platform.platform())) + + await service.get_service_properties(raw_response_hook=callback, user_agent="TestApp/v2.0") + + def test_user_agent_custom_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_user_agent_custom_async()) + + async def _test_user_agent_append_async(self): + service = FileServiceClient(self.get_file_url(), credential=self.account_key) + + def callback(response): + self.assertTrue('User-Agent' in response.http_request.headers) + self.assertEqual( + response.http_request.headers['User-Agent'], + "azsdk-python-storage-file/12.0.0b1 Python/{} ({}) customer_user_agent".format( + platform.python_version(), + platform.platform())) + + custom_headers = {'User-Agent': 'customer_user_agent'} + await service.get_service_properties(raw_response_hook=callback, headers=custom_headers) + + def test_user_agent_append_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_user_agent_append_async()) + + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-file/tests/test_file_samples_file.py b/sdk/storage/azure-storage-file/tests/test_file_samples_file.py index 7bdfe3df50b6..8e2e9f4461b6 100644 --- a/sdk/storage/azure-storage-file/tests/test_file_samples_file.py +++ b/sdk/storage/azure-storage-file/tests/test_file_samples_file.py @@ -119,7 +119,7 @@ def test_copy_from_url(self): # Copy the sample source file from the url to the destination file # [START copy_file_from_url] - destination_file.copy_file_from_url(source_url=source_url) + destination_file.start_copy_from_url(source_url=source_url) # [END copy_file_from_url] finally: # Delete the share diff --git a/sdk/storage/azure-storage-file/tests/test_file_service_properties_async.py b/sdk/storage/azure-storage-file/tests/test_file_service_properties_async.py new file mode 100644 index 000000000000..d3be2d14718d --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_file_service_properties_async.py @@ -0,0 +1,180 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +import asyncio + +from azure.core.exceptions import HttpResponseError + +from azure.storage.file.aio import ( + FileServiceClient, + Metrics, + CorsRule, + RetentionPolicy, +) + +from filetestcase import ( + FileTestCase, + TestMode, + record, + not_for_emulator, +) + + +# ------------------------------------------------------------------------------ + + +class FileServicePropertiesTest(FileTestCase): + def setUp(self): + super(FileServicePropertiesTest, self).setUp() + + url = self.get_file_url() + credential = self.get_shared_key_credential() + self.fsc = FileServiceClient(url, credential=credential) + + # --Helpers----------------------------------------------------------------- + def _assert_metrics_equal(self, metrics1, metrics2): + if metrics1 is None or metrics2 is None: + self.assertEqual(metrics1, metrics2) + return + + self.assertEqual(metrics1.version, metrics2.version) + self.assertEqual(metrics1.enabled, metrics2.enabled) + self.assertEqual(metrics1.include_apis, metrics2.include_apis) + self._assert_retention_equal(metrics1.retention_policy, metrics2.retention_policy) + + def _assert_cors_equal(self, cors1, cors2): + if cors1 is None or cors2 is None: + self.assertEqual(cors1, cors2) + return + + self.assertEqual(len(cors1), len(cors2)) + + for i in range(0, len(cors1)): + rule1 = cors1[i] + rule2 = cors2[i] + self.assertEqual(len(rule1.allowed_origins), len(rule2.allowed_origins)) + self.assertEqual(len(rule1.allowed_methods), len(rule2.allowed_methods)) + self.assertEqual(rule1.max_age_in_seconds, rule2.max_age_in_seconds) + self.assertEqual(len(rule1.exposed_headers), len(rule2.exposed_headers)) + self.assertEqual(len(rule1.allowed_headers), len(rule2.allowed_headers)) + + def _assert_retention_equal(self, ret1, ret2): + self.assertEqual(ret1.enabled, ret2.enabled) + self.assertEqual(ret1.days, ret2.days) + + # --Test cases per service --------------------------------------- + async def _test_file_service_properties_async(self): + # Arrange + + # Act + resp = await self.fsc.set_service_properties( + hour_metrics=Metrics(), minute_metrics=Metrics(), cors=list()) + + # Assert + self.assertIsNone(resp) + props = await self.fsc.get_service_properties() + self._assert_metrics_equal(props.hour_metrics, Metrics()) + self._assert_metrics_equal(props.minute_metrics, Metrics()) + self._assert_cors_equal(props.cors, list()) + + def test_file_service_properties_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_file_service_properties_async()) + + # --Test cases per feature --------------------------------------- + async def _test_set_hour_metrics_async(self): + # Arrange + hour_metrics = Metrics(enabled=True, include_apis=True, retention_policy=RetentionPolicy(enabled=True, days=5)) + + # Act + await self.fsc.set_service_properties(hour_metrics=hour_metrics) + + # Assert + received_props = await self.fsc.get_service_properties() + self._assert_metrics_equal(received_props.hour_metrics, hour_metrics) + + def test_set_hour_metrics_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_hour_metrics_async()) + + async def _test_set_minute_metrics_async(self): + # Arrange + minute_metrics = Metrics(enabled=True, include_apis=True, + retention_policy=RetentionPolicy(enabled=True, days=5)) + + # Act + await self.fsc.set_service_properties(minute_metrics=minute_metrics) + + # Assert + received_props = await self.fsc.get_service_properties() + self._assert_metrics_equal(received_props.minute_metrics, minute_metrics) + + def test_set_minute_metrics_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_minute_metrics_async()) + + async def _test_set_cors_async(self): + # Arrange + cors_rule1 = CorsRule(['www.xyz.com'], ['GET']) + + allowed_origins = ['www.xyz.com', "www.ab.com", "www.bc.com"] + allowed_methods = ['GET', 'PUT'] + max_age_in_seconds = 500 + exposed_headers = ["x-ms-meta-data*", "x-ms-meta-source*", "x-ms-meta-abc", "x-ms-meta-bcd"] + allowed_headers = ["x-ms-meta-data*", "x-ms-meta-target*", "x-ms-meta-xyz", "x-ms-meta-foo"] + cors_rule2 = CorsRule( + allowed_origins, + allowed_methods, + max_age_in_seconds=max_age_in_seconds, + exposed_headers=exposed_headers, + allowed_headers=allowed_headers) + + cors = [cors_rule1, cors_rule2] + + # Act + await self.fsc.set_service_properties(cors=cors) + + # Assert + received_props = await self.fsc.get_service_properties() + self._assert_cors_equal(received_props.cors, cors) + + def test_set_cors_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_cors_async()) + + # --Test cases for errors --------------------------------------- + + + async def _test_too_many_cors_rules_async(self): + # Arrange + cors = [] + for i in range(0, 6): + cors.append(CorsRule(['www.xyz.com'], ['GET'])) + + # Assert + with self.assertRaises(HttpResponseError): + await self.fsc.set_service_properties(None, None, cors) + + def test_too_many_cors_rules_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_too_many_cors_rules_async()) + + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-file/tests/test_get_file.py b/sdk/storage/azure-storage-file/tests/test_get_file.py index 28f550ca0bfc..16a201a1f566 100644 --- a/sdk/storage/azure-storage-file/tests/test_get_file.py +++ b/sdk/storage/azure-storage-file/tests/test_get_file.py @@ -279,6 +279,29 @@ def callback(response): self.MAX_SINGLE_GET_SIZE, progress) + def test_get_file_with_iter(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + for data in file_client.download_file(): + stream.write(data) + # Assert + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + def test_get_file_to_stream(self): # parallel tests introduce random order of requests, can only run live if TestMode.need_recording_file(self.test_mode): diff --git a/sdk/storage/azure-storage-file/tests/test_get_file_async.py b/sdk/storage/azure-storage-file/tests/test_get_file_async.py new file mode 100644 index 000000000000..6346a0b6168a --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_get_file_async.py @@ -0,0 +1,1566 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import base64 +import os +import unittest +import asyncio + +import pytest +from azure.core.exceptions import HttpResponseError + +from azure.storage.file.aio import ( + FileClient, + FileServiceClient, + FileProperties +) +from filetestcase import ( + FileTestCase, + TestMode, + record, +) + +# ------------------------------------------------------------------------------ +TEST_FILE_PREFIX = 'file' +FILE_PATH = 'file_output.temp.dat' + + +# ------------------------------------------------------------------------------ + +class StorageGetFileTest(FileTestCase): + def setUp(self): + super(StorageGetFileTest, self).setUp() + + # test chunking functionality by reducing the threshold + # for chunking and the size of each chunk, otherwise + # the tests would take too long to execute + self.MAX_SINGLE_GET_SIZE = 32 * 1024 + self.MAX_CHUNK_GET_SIZE = 4 * 1024 + + url = self.get_file_url() + credential = self.get_shared_key_credential() + + self.fsc = FileServiceClient( + url, credential=credential, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + self.share_name = self.get_resource_name('utshare') + self.directory_name = self.get_resource_name('utdir') + self.byte_file = self.get_resource_name('bytefile') + self.byte_data = self.get_random_bytes(64 * 1024 + 5) + + + def tearDown(self): + if not self.is_playback(): + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(self.fsc.delete_share(self.share_name, delete_snapshots='include')) + except: + pass + + if os.path.isfile(FILE_PATH): + try: + os.remove(FILE_PATH) + except: + pass + + return super(StorageGetFileTest, self).tearDown() + + # --Helpers----------------------------------------------------------------- + + def _get_file_reference(self): + return self.get_resource_name(TEST_FILE_PREFIX) + + async def _setup(self): + if not self.is_playback(): + try: + share = await self.fsc.create_share(self.share_name) + await share.create_directory(self.directory_name) + except: + pass + byte_file = self.directory_name + '/' + self.byte_file + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=byte_file, + credential=self.get_shared_key_credential() + ) + try: + await file_client.upload_file(self.byte_data) + except: + pass + + class NonSeekableFile(object): + def __init__(self, wrapped_file): + self.wrapped_file = wrapped_file + + def write(self, data): + self.wrapped_file.write(data) + + def read(self, count): + return self.wrapped_file.read(count) + + def seekable(self): + return False + + # -- Get test cases for files ---------------------------------------------- + + async def _test_unicode_get_file_unicode_data_async(self): + # Arrange + await self._setup() + file_data = u'hello world啊齄丂狛狜'.encode('utf-8') + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + # Act + file_content = await file_client.download_file() + file_content = await file_content.content_as_bytes() + + # Assert + self.assertEqual(file_content, file_data) + + def test_unicode_get_file_unicode_data_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_unicode_get_file_unicode_data_async()) + + async def _test_unicode_get_file_binary_data_async(self): + # Arrange + await self._setup() + base64_data = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==' + binary_data = base64.b64decode(base64_data) + + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(binary_data) + + # Act + file_content = await file_client.download_file() + file_content = await file_content.content_as_bytes() + + # Assert + self.assertEqual(file_content, binary_data) + + def test_unicode_get_file_binary_data_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_unicode_get_file_binary_data_async()) + + async def _test_get_file_no_content_async(self): + # Arrange + await self._setup() + file_data = b'' + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + # Act + file_output = await file_client.download_file() + file_content = await file_output.content_as_bytes() + + # Assert + self.assertEqual(file_data, file_content) + self.assertEqual(0, file_output.properties.size) + + def test_get_file_no_content_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_no_content_async()) + + async def _test_get_file_to_bytes_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + file_output = await file_client.download_file() + file_content = await file_output.content_as_bytes(max_connections=2) + + # Assert + self.assertEqual(self.byte_data, file_content) + + def test_get_file_to_bytes_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_bytes_async()) + + async def _test_get_file_to_bytes_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_output = await file_client.download_file(raw_response_hook=callback) + file_content = await file_output.content_as_bytes(max_connections=2) + + # Assert + self.assertEqual(self.byte_data, file_content) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_bytes_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_bytes_with_progress_async()) + + async def _test_get_file_to_bytes_non_parallel_async(self): + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_output = await file_client.download_file(raw_response_hook=callback) + file_content = await file_output.content_as_bytes() + + # Assert + self.assertEqual(self.byte_data, file_content) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_bytes_non_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_bytes_non_parallel_async()) + + async def _test_get_file_to_bytes_small_async(self): + # Arrange + await self._setup() + file_data = self.get_random_bytes(1024) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_output = await file_client.download_file(raw_response_hook=callback) + file_content = await file_output.content_as_bytes() + + # Assert + self.assertEqual(file_data, file_content) + self.assert_download_progress( + len(file_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_bytes_small_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_bytes_small_async()) + + async def _test_get_file_to_stream_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file() + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + + def test_get_file_to_stream_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_async()) + + async def _test_get_file_with_iter_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + download = await file_client.download_file() + async for data in download: + stream.write(data) + # Assert + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + + def test_get_file_with_iter_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_with_iter_async()) + + async def _test_get_file_to_stream_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_with_progress_async()) + + async def _test_get_file_to_stream_non_parallel_async(self): + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_non_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_non_parallel_async()) + + async def _test_get_file_to_stream_small_async(self): + # Arrange + await self._setup() + file_data = self.get_random_bytes(1024) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(file_data, actual) + self.assert_download_progress( + len(file_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_small_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_small_async()) + + async def _test_get_file_to_stream_from_snapshot_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await snapshot_client.download_file() + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + + def test_get_file_to_stream_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_from_snapshot_async()) + + async def _test_get_file_to_stream_with_progress_from_snapshot_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await snapshot_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_with_progress_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_with_progress_from_snapshot_async()) + + async def _test_get_file_to_stream_non_parallel_from_snapshot_async(self): + # Arrange + await self._setup() + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await snapshot_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + self.assert_download_progress( + len(self.byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_non_parallel_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_non_parallel_from_snapshot_async()) + + async def _test_get_file_to_stream_small_from_snapshot_async(self): + # Arrange + await self._setup() + file_data = self.get_random_bytes(1024) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.upload_file(file_data) + + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await snapshot_client.download_file(raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(file_data, actual) + self.assert_download_progress( + len(file_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_stream_small_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_stream_small_from_snapshot_async()) + + async def _test_ranged_get_file_to_path_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + end_range = self.MAX_SINGLE_GET_SIZE + 1024 + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=1, length=end_range) + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data[1:end_range + 1], actual) + + def test_ranged_get_file_to_path_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_async()) + + async def _test_ranged_get_file_to_path_with_single_byte_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + end_range = self.MAX_SINGLE_GET_SIZE + 1024 + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=0, length=0) + props = await props.download_to_stream(stream) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(1, len(actual)) + self.assertEqual(self.byte_data[0], actual[0]) + + def test_ranged_get_file_to_path_with_single_byte_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_with_single_byte_async()) + + async def _test_ranged_get_file_to_bytes_with_zero_byte_async(self): + # Arrange + await self._setup() + file_data = b'' + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + # Act + # the get request should fail in this case since the blob is empty and yet there is a range specified + with self.assertRaises(HttpResponseError): + props = await file_client.download_file(offset=0, length=5) + await props.content_as_bytes() + + with self.assertRaises(HttpResponseError): + props = await file_client.download_file(offset=3, length=5) + await props.content_as_bytes() + + def test_ranged_get_file_to_bytes_with_zero_byte_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_bytes_with_zero_byte_async()) + + async def _test_ranged_get_file_to_path_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + start_range = 3 + end_range = self.MAX_SINGLE_GET_SIZE + 1024 + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=start_range, length=end_range, raw_response_hook=callback) + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data[start_range:end_range + 1], actual) + self.assert_download_progress( + end_range - start_range + 1, + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_ranged_get_file_to_path_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_with_progress_async()) + + async def _test_ranged_get_file_to_path_small_async(self): + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=1, length=4) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data[1:5], actual) + + def test_ranged_get_file_to_path_small_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_small_async()) + + async def _test_ranged_get_file_to_path_non_parallel_async(self): + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=1, length=3) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data[1:4], actual) + + def test_ranged_get_file_to_path_non_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_non_parallel_async()) + + async def _test_ranged_get_file_to_path_invalid_range_parallel_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_size = self.MAX_SINGLE_GET_SIZE + 1 + file_data = self.get_random_bytes(file_size) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + # Act + end_range = 2 * self.MAX_SINGLE_GET_SIZE + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=1, length=end_range) + props = await props.download_to_stream(stream, max_connections=2) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(file_data[1:file_size], actual) + + def test_ranged_get_file_to_path_invalid_range_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_invalid_range_parallel_async()) + + async def _test_ranged_get_file_to_path_invalid_range_non_parallel_async(self): + + # Arrange + await self._setup() + file_size = 1024 + file_data = self.get_random_bytes(file_size) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + # Act + end_range = 2 * self.MAX_SINGLE_GET_SIZE + with open(FILE_PATH, 'wb') as stream: + props = await file_client.download_file(offset=1, length=end_range) + props = await props.download_to_stream(stream, max_connections=1) + + # Assert + self.assertIsInstance(props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(file_data[1:file_size], actual) + + def test_ranged_get_file_to_path_invalid_range_non_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_ranged_get_file_to_path_invalid_range_non_parallel_async()) + + async def _test_get_file_to_text_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + text_file = self.get_resource_name('textfile') + text_data = self.get_random_text_data(self.MAX_SINGLE_GET_SIZE + 1) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + text_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(text_data) + + # Act + file_content = await file_client.download_file() + file_content = await file_content.content_as_text(max_connections=2) + + # Assert + self.assertEqual(text_data, file_content) + + def test_get_file_to_text_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_async()) + + async def _test_get_file_to_text_with_progress_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + text_file = self.get_resource_name('textfile') + text_data = self.get_random_text_data(self.MAX_SINGLE_GET_SIZE + 1) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + text_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(text_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_content = await file_client.download_file(raw_response_hook=callback) + file_content = await file_content.content_as_text(max_connections=2) + + # Assert + self.assertEqual(text_data, file_content) + self.assert_download_progress( + len(text_data.encode('utf-8')), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_text_with_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_with_progress_async()) + + async def _test_get_file_to_text_non_parallel_async(self): + # Arrange + await self._setup() + text_file = self._get_file_reference() + text_data = self.get_random_text_data(self.MAX_SINGLE_GET_SIZE + 1) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + text_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(text_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_content = await file_client.download_file(raw_response_hook=callback) + file_content = await file_content.content_as_text(max_connections=1) + + # Assert + self.assertEqual(text_data, file_content) + self.assert_download_progress( + len(text_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_text_non_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_non_parallel_async()) + + async def _test_get_file_to_text_small_async(self): + # Arrange + await self._setup() + file_data = self.get_random_text_data(1024) + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(file_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_content = await file_client.download_file(raw_response_hook=callback) + file_content = await file_content.content_as_text() + + # Assert + self.assertEqual(file_data, file_content) + self.assert_download_progress( + len(file_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_text_small_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_small_async()) + + async def _test_get_file_to_text_with_encoding_async(self): + # Arrange + await self._setup() + text = u'hello 啊齄丂狛狜 world' + data = text.encode('utf-16') + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(data) + + # Act + file_content = await file_client.download_file() + file_content = await file_content.content_as_text(encoding='UTF-16') + + # Assert + self.assertEqual(text, file_content) + + def test_get_file_to_text_with_encoding_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_with_encoding_async()) + + async def _test_get_file_to_text_with_encoding_and_progress_async(self): + # Arrange + await self._setup() + text = u'hello 啊齄丂狛狜 world' + data = text.encode('utf-16') + file_name = self._get_file_reference() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(data) + + # Act + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + file_content = await file_client.download_file(raw_response_hook=callback) + file_content = await file_content.content_as_text(encoding='UTF-16') + + # Assert + self.assertEqual(text, file_content) + self.assert_download_progress( + len(data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_to_text_with_encoding_and_progress_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_to_text_with_encoding_and_progress_async()) + + async def _test_get_file_non_seekable_async(self): + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + non_seekable_stream = StorageGetFileTest.NonSeekableFile(stream) + file_props = await file_client.download_file() + file_props = await file_props.download_to_stream(non_seekable_stream, max_connections=1) + + # Assert + self.assertIsInstance(file_props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + + def test_get_file_non_seekable_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_non_seekable_async()) + + async def _test_get_file_non_seekable_parallel_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + non_seekable_stream = StorageGetFileTest.NonSeekableFile(stream) + + with self.assertRaises(ValueError): + data = await file_client.download_file() + await data.download_to_stream(non_seekable_stream, max_connections=2) + + # Assert + + def test_get_file_non_seekable_parallel_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_non_seekable_parallel_async()) + + async def _test_get_file_non_seekable_from_snapshot_async(self): + # Arrange + await self._setup() + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + non_seekable_stream = StorageGetFileTest.NonSeekableFile(stream) + file_props = await snapshot_client.download_file() + file_props = await file_props.download_to_stream(non_seekable_stream, max_connections=1) + + # Assert + self.assertIsInstance(file_props, FileProperties) + with open(FILE_PATH, 'rb') as stream: + actual = stream.read() + self.assertEqual(self.byte_data, actual) + + def test_get_file_non_seekable_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_non_seekable_from_snapshot_async()) + + async def _test_get_file_non_seekable_parallel_from_snapshot_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + # Create a snapshot of the share and delete the file + share_client = self.fsc.get_share_client(self.share_name) + share_snapshot = await share_client.create_snapshot() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY) + await file_client.delete_file() + + snapshot_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + snapshot=share_snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + with open(FILE_PATH, 'wb') as stream: + non_seekable_stream = StorageGetFileTest.NonSeekableFile(stream) + + with self.assertRaises(ValueError): + data = await snapshot_client.download_file() + await data.download_to_stream(non_seekable_stream, max_connections=2) + + def test_get_file_non_seekable_parallel_from_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_non_seekable_parallel_from_snapshot_async()) + + async def _test_get_file_exact_get_size_async(self): + # Arrange + await self._setup() + file_name = self._get_file_reference() + byte_data = self.get_random_bytes(self.MAX_SINGLE_GET_SIZE) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(byte_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_content = await file_client.download_file(raw_response_hook=callback) + file_bytes = await file_content.content_as_bytes() + + # Assert + self.assertEqual(byte_data, file_bytes) + self.assert_download_progress( + len(byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_exact_get_size_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_exact_get_size_async()) + + async def _test_get_file_exact_chunk_size_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_name = self._get_file_reference() + byte_data = self.get_random_bytes(self.MAX_SINGLE_GET_SIZE + self.MAX_CHUNK_GET_SIZE) + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + file_name, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + await file_client.upload_file(byte_data) + + progress = [] + def callback(response): + current = response.context['download_stream_current'] + total = response.context['data_stream_total'] + if current is not None: + progress.append((current, total)) + + # Act + file_content = await file_client.download_file(raw_response_hook=callback) + file_bytes = await file_content.content_as_bytes(max_connections=2) + + # Assert + self.assertEqual(byte_data, file_bytes) + self.assert_download_progress( + len(byte_data), + self.MAX_CHUNK_GET_SIZE, + self.MAX_SINGLE_GET_SIZE, + progress) + + def test_get_file_exact_chunk_size_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_exact_chunk_size_async()) + + async def _test_get_file_with_md5_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + file_content = await file_client.download_file(validate_content=True) + file_bytes = await file_content.content_as_bytes() + + # Assert + self.assertEqual(self.byte_data, file_bytes) + + def test_get_file_with_md5_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_with_md5_async()) + + async def _test_get_file_range_with_md5_async(self): + # parallel tests introduce random order of requests, can only run live + if TestMode.need_recording_file(self.test_mode): + return + + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + file_content = await file_client.download_file(offset=0, length=1024, validate_content=True) + + # Assert + self.assertIsNone(file_content.properties.content_settings.content_md5) + + # Arrange + props = await file_client.get_file_properties() + props.content_settings.content_md5 = b'MDAwMDAwMDA=' + await file_client.set_http_headers(props.content_settings) + + # Act + file_content = await file_client.download_file(offset=0, length=1024, validate_content=True) + + # Assert + self.assertEqual(b'MDAwMDAwMDA=', file_content.properties.content_settings.content_md5) + + def test_get_file_range_with_md5_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_range_with_md5_async()) + + async def _test_get_file_server_encryption_async(self): + + #Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + file_content = await file_client.download_file(offset=0, length=1024, validate_content=True) + + # Assert + if self.is_file_encryption_enabled(): + self.assertTrue(file_content.properties.server_encrypted) + else: + self.assertFalse(file_content.properties.server_encrypted) + + def test_get_file_server_encryption_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_server_encryption_async()) + + async def _test_get_file_properties_server_encryption_async(self): + + # Arrange + await self._setup() + file_client = FileClient( + self.get_file_url(), + share=self.share_name, + file_path=self.directory_name + '/' + self.byte_file, + credential=self.settings.STORAGE_ACCOUNT_KEY, + max_single_get_size=self.MAX_SINGLE_GET_SIZE, + max_chunk_get_size=self.MAX_CHUNK_GET_SIZE) + + # Act + props = await file_client.get_file_properties() + + # Assert + if self.is_file_encryption_enabled(): + self.assertTrue(props.server_encrypted) + else: + self.assertFalse(props.server_encrypted) + + def test_get_file_properties_server_encryption_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_file_properties_server_encryption_async()) + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-file/tests/test_share_async.py b/sdk/storage/azure-storage-file/tests/test_share_async.py new file mode 100644 index 000000000000..684cc7c72d78 --- /dev/null +++ b/sdk/storage/azure-storage-file/tests/test_share_async.py @@ -0,0 +1,914 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import unittest +from datetime import datetime, timedelta +import asyncio +import pytest +import requests +from azure.core.exceptions import ( + HttpResponseError, + ResourceNotFoundError, + ResourceExistsError) + +from azure.storage.file.aio import ( + AccessPolicy, + SharePermissions, + FileServiceClient, + DirectoryClient, + FileClient, + ShareClient) +from azure.storage.file._generated.models import DeleteSnapshotsOptionType, ListSharesIncludeType +from filetestcase import ( + FileTestCase, + TestMode, + record, + LogCaptured, +) + +# ------------------------------------------------------------------------------ +TEST_SHARE_PREFIX = 'share' + + +# ------------------------------------------------------------------------------ + +class StorageShareTest(FileTestCase): + def setUp(self): + super(StorageShareTest, self).setUp() + + file_url = self.get_file_url() + credentials = self.get_shared_key_credential() + self.fsc = FileServiceClient(account_url=file_url, credential=credentials) + self.test_shares = [] + + def tearDown(self): + if not self.is_playback(): + loop = asyncio.get_event_loop() + try: + for s in self.test_shares: + loop.run_until_complete(self.fsc.delete_share(s.share_name, delete_snapshots=True)) + except: + pass + return super(StorageShareTest, self).tearDown() + + # --Helpers----------------------------------------------------------------- + def _get_share_reference(self, prefix=TEST_SHARE_PREFIX): + share_name = self.get_resource_name(prefix) + share = self.fsc.get_share_client(share_name) + self.test_shares.append(share) + return share + + async def _create_share(self, prefix=TEST_SHARE_PREFIX): + share_client = self._get_share_reference(prefix) + share = await share_client.create_share() + return share_client + + # --Test cases for shares ----------------------------------------- + async def _test_create_share_async(self): + # Arrange + share = self._get_share_reference() + + # Act + created = await share.create_share() + + # Assert + self.assertTrue(created) + + def test_create_share_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_async()) + + async def _test_create_share_snapshot_async(self): + # Arrange + share = self._get_share_reference() + + # Act + created = await share.create_share() + snapshot = await share.create_snapshot() + + # Assert + self.assertTrue(created) + self.assertIsNotNone(snapshot['snapshot']) + self.assertIsNotNone(snapshot['etag']) + self.assertIsNotNone(snapshot['last_modified']) + + def test_create_share_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_snapshot_async()) + + async def _test_create_snapshot_with_metadata_async(self): + # Arrange + share = self._get_share_reference() + metadata = {"test1": "foo", "test2": "bar"} + metadata2 = {"test100": "foo100", "test200": "bar200"} + + # Act + created = await share.create_share(metadata=metadata) + snapshot = await share.create_snapshot(metadata=metadata2) + + share_props = await share.get_share_properties() + snapshot_client = ShareClient( + self.get_file_url(), + share=share.share_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY + ) + snapshot_props = await snapshot_client.get_share_properties() + # Assert + self.assertTrue(created) + self.assertIsNotNone(snapshot['snapshot']) + self.assertIsNotNone(snapshot['etag']) + self.assertIsNotNone(snapshot['last_modified']) + self.assertEqual(share_props.metadata, metadata) + self.assertEqual(snapshot_props.metadata, metadata2) + + def test_create_snapshot_with_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_snapshot_with_metadata_async()) + + async def _test_delete_share_with_snapshots_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + snapshot = await share.create_snapshot() + + # Act + with self.assertRaises(HttpResponseError): + await share.delete_share() + + deleted = await share.delete_share(delete_snapshots=True) + self.assertIsNone(deleted) + + def test_delete_share_with_snapshots_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_share_with_snapshots_async()) + + async def _test_delete_snapshot_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + snapshot = await share.create_snapshot() + + # Act + with self.assertRaises(HttpResponseError): + await share.delete_share() + + snapshot_client = ShareClient( + self.get_file_url(), + share=share.share_name, + snapshot=snapshot, + credential=self.settings.STORAGE_ACCOUNT_KEY + ) + + deleted = await snapshot_client.delete_share() + self.assertIsNone(deleted) + + def test_delete_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_snapshot_async()) + + async def _test_create_share_fail_on_exist(self): + # Arrange + share = self._get_share_reference() + + # Act + created = await share.create_share() + + # Assert + self.assertTrue(created) + + def test_create_share_fail_on_exist(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_fail_on_exist()) + + async def _test_create_share_with_already_existing_share_fail_on_exist_async(self): + # Arrange + share = self._get_share_reference() + + # Act + created = await share.create_share() + with self.assertRaises(HttpResponseError): + await share.create_share() + + # Assert + self.assertTrue(created) + + def test_create_share_with_already_existing_share_fail_on_exist_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_with_already_existing_share_fail_on_exist_async()) + + async def _test_create_share_with_metadata_async(self): + # Arrange + metadata = {'hello': 'world', 'number': '42'} + + # Act + client = self._get_share_reference() + created = await client.create_share(metadata) + + # Assert + self.assertTrue(created) + props = await client.get_share_properties() + self.assertDictEqual(props.metadata, metadata) + + def test_create_share_with_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_with_metadata_async()) + + async def _test_create_share_with_quota_async(self): + # Arrange + + # Act + client = self._get_share_reference() + created = await client.create_share(quota=1) + + # Assert + props = await client.get_share_properties() + self.assertTrue(created) + self.assertEqual(props.quota, 1) + + def test_create_share_with_quota_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_create_share_with_quota_async()) + + async def _test_share_exists_async(self): + # Arrange + share = await self._create_share() + + # Act + exists = await share.get_share_properties() + + # Assert + self.assertTrue(exists) + + def test_share_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_share_exists_async()) + + async def _test_share_not_exists_async(self): + # Arrange + share = self._get_share_reference() + + # Act + with self.assertRaises(ResourceNotFoundError): + await share.get_share_properties() + + # Assert + + def test_share_not_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_share_not_exists_async()) + + async def _test_share_snapshot_exists_async(self): + # Arrange + share = await self._create_share() + snapshot = await share.create_snapshot() + + # Act + snapshot_client = self.fsc.get_share_client(share.share_name, snapshot=snapshot) + exists = await snapshot_client.get_share_properties() + + # Assert + self.assertTrue(exists) + + def test_share_snapshot_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_share_snapshot_exists_async()) + + async def _test_share_snapshot_not_exists_async(self): + # Arrange + share = await self._create_share() + made_up_snapshot = '2017-07-19T06:53:46.0000000Z' + + # Act + snapshot_client = self.fsc.get_share_client(share.share_name, snapshot=made_up_snapshot) + with self.assertRaises(ResourceNotFoundError): + await snapshot_client.get_share_properties() + + # Assert + + def test_share_snapshot_not_exists_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_share_snapshot_not_exists_async()) + + async def _test_unicode_create_share_unicode_name_async(self): + # Arrange + share_name = u'啊齄丂狛狜' + + # Act + with self.assertRaises(HttpResponseError): + # not supported - share name must be alphanumeric, lowercase + client = self.fsc.get_share_client(share_name) + await client.create_share() + + # Assert + + def test_unicode_create_share_unicode_name_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_unicode_create_share_unicode_name_async()) + + async def _test_list_shares_no_options_async(self): + # Arrange + share = await self._create_share() + # Act + shares = [] + async for s in self.fsc.list_shares(): + shares.append(s) + + # Assert + self.assertIsNotNone(shares) + self.assertGreaterEqual(len(shares), 1) + self.assertIsNotNone(shares[0]) + self.assertNamedItemInContainer(shares, share.share_name) + + def test_list_shares_no_options_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_shares_no_options_async()) + + async def _test_list_shares_with_snapshot_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + snapshot1 = await share.create_snapshot() + snapshot2 = await share.create_snapshot() + + # Act + shares = self.fsc.list_shares(include_snapshots=True) + + # Assert + self.assertIsNotNone(shares) + all_shares = [] + async for s in shares: + all_shares.append(s) + self.assertEqual(len(all_shares), 3) + self.assertNamedItemInContainer(all_shares, share.share_name) + self.assertNamedItemInContainer(all_shares, snapshot1['snapshot']) + self.assertNamedItemInContainer(all_shares, snapshot2['snapshot']) + + def test_list_shares_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_shares_with_snapshot_async()) + + async def _test_list_shares_with_prefix_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + shares = [] + async for s in self.fsc.list_shares(name_starts_with=share.share_name): + shares.append(s) + + # Assert + self.assertEqual(len(shares), 1) + self.assertIsNotNone(shares[0]) + self.assertEqual(shares[0].name, share.share_name) + self.assertIsNone(shares[0].metadata) + + def test_list_shares_with_prefix_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_shares_with_prefix_async()) + + async def _test_list_shares_with_include_metadata_async(self): + # Arrange + metadata = {'hello': 'world', 'number': '42'} + share = self._get_share_reference() + await share.create_share(metadata) + + # Act + shares = [] + async for s in self.fsc.list_shares(share.share_name, include_metadata=True): + shares.append(s) + + # Assert + self.assertIsNotNone(shares) + self.assertGreaterEqual(len(shares), 1) + self.assertIsNotNone(shares[0]) + self.assertNamedItemInContainer(shares, share.share_name) + self.assertDictEqual(shares[0].metadata, metadata) + + def test_list_shares_with_include_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_shares_with_include_metadata_async()) + + async def _test_list_shares_with_num_results_and_marker_async(self): + # Arrange + prefix = 'listshare' + share_names = [] + for i in range(0, 4): + share = await self._create_share(prefix + str(i)) + share_names.append(share.share_name) + + share_names.sort() + + # Act + generator1 = self.fsc.list_shares(prefix, results_per_page=2) + await generator1.__anext__() + generator2 = self.fsc.list_shares( + prefix, marker=generator1.next_marker, results_per_page=2) + await generator2.__anext__() + + shares1 = generator1.current_page + shares2 = generator2.current_page + + # Assert + self.assertIsNotNone(shares1) + self.assertEqual(len(shares1), 2) + self.assertNamedItemInContainer(shares1, share_names[0]) + self.assertNamedItemInContainer(shares1, share_names[1]) + self.assertIsNotNone(shares2) + self.assertEqual(len(shares2), 2) + self.assertNamedItemInContainer(shares2, share_names[2]) + self.assertNamedItemInContainer(shares2, share_names[3]) + + def test_list_shares_with_num_results_and_marker_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_shares_with_num_results_and_marker_async()) + + async def _test_set_share_metadata_async(self): + # Arrange + share = await self._create_share() + metadata = {'hello': 'world', 'number': '42'} + + # Act + await share.set_share_metadata(metadata) + + # Assert + props = await share.get_share_properties() + md = props.metadata + self.assertDictEqual(md, metadata) + + def test_set_share_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_metadata_async()) + + async def _test_get_share_metadata_async(self): + # Arrange + metadata = {'hello': 'world', 'number': '42'} + + # Act + client = self._get_share_reference() + created = await client.create_share(metadata) + + # Assert + self.assertTrue(created) + props = await client.get_share_properties() + self.assertDictEqual(props.metadata, metadata) + + def test_get_share_metadata_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_share_metadata_async()) + + async def _test_get_share_metadata_with_snapshot_async(self): + # Arrange + metadata = {'hello': 'world', 'number': '42'} + + # Act + client = self._get_share_reference() + created = await client.create_share(metadata) + snapshot = await client.create_snapshot() + snapshot_client = self.fsc.get_share_client(client.share_name, snapshot=snapshot) + + # Assert + self.assertTrue(created) + props = await snapshot_client.get_share_properties() + self.assertDictEqual(props.metadata, metadata) + + def test_get_share_metadata_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_share_metadata_with_snapshot_async()) + + async def _test_set_share_properties_async(self): + # Arrange + share = await self._create_share() + await share.set_share_quota(1) + + # Act + props = await share.get_share_properties() + + # Assert + self.assertIsNotNone(props) + self.assertEqual(props.quota, 1) + + def test_set_share_properties_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_properties_async()) + + async def _test_delete_share_with_existing_share_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + deleted = await share.delete_share() + + # Assert + self.assertIsNone(deleted) + + def test_delete_share_with_existing_share_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_share_with_existing_share_async()) + + async def _test_delete_share_with_existing_share_fail_not_exist_async(self): + # Arrange + client = self._get_share_reference() + + # Act + with LogCaptured(self) as log_captured: + with self.assertRaises(HttpResponseError): + await client.delete_share() + + log_as_str = log_captured.getvalue() + + def test_delete_share_with_existing_share_fail_not_exist_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_share_with_existing_share_fail_not_exist_async()) + + async def _test_delete_share_with_non_existing_share_async(self): + # Arrange + client = self._get_share_reference() + + # Act + with LogCaptured(self) as log_captured: + with self.assertRaises(HttpResponseError): + deleted = await client.delete_share() + + log_as_str = log_captured.getvalue() + self.assertTrue('ERROR' not in log_as_str) + + def test_delete_share_with_non_existing_share_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_share_with_non_existing_share_async()) + + async def _test_delete_share_with_non_existing_share_fail_not_exist_async(self): + # Arrange + client = self._get_share_reference() + + # Act + with LogCaptured(self) as log_captured: + with self.assertRaises(HttpResponseError): + await client.delete_share() + + log_as_str = log_captured.getvalue() + + def test_delete_share_with_non_existing_share_fail_not_exist_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_delete_share_with_non_existing_share_fail_not_exist_async()) + + async def _test_get_share_stats_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + share_usage = await share.get_share_stats() + + # Assert + self.assertEqual(share_usage, 0) + + def test_get_share_stats_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_get_share_stats_async()) + + async def _test_set_share_acl_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + resp = await share.set_share_access_policy() + + # Assert + acl = await share.get_share_access_policy() + self.assertIsNotNone(acl) + + def test_set_share_acl_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_acl_async()) + + async def _test_set_share_acl_with_empty_signed_identifiers_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + resp = await share.set_share_access_policy(dict()) + + # Assert + acl = await share.get_share_access_policy() + self.assertIsNotNone(acl) + self.assertEqual(len(acl.get('signed_identifiers')), 0) + + def test_set_share_acl_with_empty_signed_identifiers_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_acl_with_empty_signed_identifiers_async()) + + async def _test_set_share_acl_with_signed_identifiers_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + identifiers = dict() + identifiers['testid'] = AccessPolicy( + permission=SharePermissions.WRITE, + expiry=datetime.utcnow() + timedelta(hours=1), + start=datetime.utcnow() - timedelta(minutes=1), + ) + + resp = await share.set_share_access_policy(identifiers) + + # Assert + acl = await share.get_share_access_policy() + self.assertIsNotNone(acl) + self.assertEqual(len(acl['signed_identifiers']), 1) + self.assertEqual(acl['signed_identifiers'][0].id, 'testid') + + def test_set_share_acl_with_signed_identifiers_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_acl_with_signed_identifiers_async()) + + async def _test_set_share_acl_too_many_ids_async(self): + # Arrange + share = self._get_share_reference() + await share.create_share() + + # Act + identifiers = dict() + for i in range(0, 6): + identifiers['id{}'.format(i)] = AccessPolicy() + + # Assert + with self.assertRaises(ValueError) as e: + await share.set_share_access_policy(identifiers) + self.assertEqual( + str(e.exception), + 'Too many access policies provided. The server does not support setting more than 5 access policies on a single resource.' + ) + + def test_set_share_acl_too_many_ids_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_set_share_acl_too_many_ids_async()) + + async def _test_list_directories_and_files_async(self): + # Arrange + share = await self._create_share() + dir0 = share.get_directory_client() + await dir0.upload_file('file1', 'data1') + dir1 = share.get_directory_client('dir1') + await dir1.create_directory() + await dir1.upload_file('file2', 'data2') + dir2 = share.get_directory_client('dir2') + await dir2.create_directory() + + # Act + resp = [] + async for d in share.list_directories_and_files(): + resp.append(d) + + # Assert + self.assertIsNotNone(resp) + self.assertEqual(len(resp), 3) + self.assertIsNotNone(resp[0]) + self.assertNamedItemInContainer(resp, 'dir1') + self.assertNamedItemInContainer(resp, 'dir2') + self.assertNamedItemInContainer(resp, 'file1') + + def test_list_directories_and_files_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_directories_and_files_async()) + + async def _test_list_directories_and_files_with_snapshot_async(self): + # Arrange + share_name = await self._create_share() + dir1 = share_name.get_directory_client('dir1') + await dir1.create_directory() + dir2 = share_name.get_directory_client('dir2') + await dir2.create_directory() + snapshot1 = await share_name.create_snapshot() + dir3 = share_name.get_directory_client('dir3') + await dir3.create_directory() + file1 = share_name.get_file_client('file1') + await file1.upload_file('data') + + + # Act + snapshot_client = self.fsc.get_share_client(share_name.share_name, snapshot=snapshot1) + resp = [] + async for d in snapshot_client.list_directories_and_files(): + resp.append(d) + + # Assert + self.assertIsNotNone(resp) + self.assertEqual(len(resp), 2) + self.assertIsNotNone(resp[0]) + self.assertNamedItemInContainer(resp, 'dir1') + self.assertNamedItemInContainer(resp, 'dir2') + + def test_list_directories_and_files_with_snapshot_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_directories_and_files_with_snapshot_async()) + + async def _test_list_directories_and_files_with_num_results_async(self): + # Arrange + share_name = await self._create_share() + dir1 = await share_name.create_directory('dir1') + root = share_name.get_directory_client() + await root.upload_file('filea1', '1024') + await root.upload_file('filea2', '1024') + await root.upload_file('filea3', '1024') + await root.upload_file('fileb1', '1024') + + # Act + result = share_name.list_directories_and_files(results_per_page=2) + await result.__anext__() + + # Assert + self.assertIsNotNone(result) + self.assertEqual(len(result.current_page), 2) + self.assertNamedItemInContainer(result.current_page, 'dir1') + self.assertNamedItemInContainer(result.current_page, 'filea1') + + def test_list_directories_and_files_with_num_results_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_directories_and_files_with_num_results_async()) + + async def _test_list_directories_and_files_with_num_results_and_marker_async(self): + # Arrange + share_name = await self._create_share() + dir1 = share_name.get_directory_client('dir1') + await dir1.create_directory() + await dir1.upload_file('filea1', '1024') + await dir1.upload_file('filea2', '1024') + await dir1.upload_file('filea3', '1024') + await dir1.upload_file('fileb1', '1024') + + # Act + generator1 = share_name.list_directories_and_files('dir1', results_per_page=2) + await generator1.__anext__() + generator2 = share_name.list_directories_and_files( + 'dir1', marker=generator1.next_marker, results_per_page=2) + await generator2.__anext__() + + result1 = generator1.current_page + result2 = generator2.current_page + + # Assert + self.assertEqual(len(result1), 2) + self.assertEqual(len(result2), 2) + self.assertNamedItemInContainer(result1, 'filea1') + self.assertNamedItemInContainer(result1, 'filea2') + self.assertNamedItemInContainer(result2, 'filea3') + self.assertNamedItemInContainer(result2, 'fileb1') + self.assertEqual(generator2.next_marker, None) + + def test_list_directories_and_files_with_num_results_and_marker_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_directories_and_files_with_num_results_and_marker_async()) + + async def _test_list_directories_and_files_with_prefix_async(self): + # Arrange + share = await self._create_share() + dir1 = await share.create_directory('dir1') + await share.create_directory('dir1/pref_dir3') + await share.create_directory('dir2') + + root = share.get_directory_client() + await root.upload_file('file1', '1024') + await dir1.upload_file('pref_file2', '1025') + await dir1.upload_file('file3', '1025') + + # Act + resp = [] + async for d in share.list_directories_and_files('dir1', name_starts_with='pref'): + resp.append(d) + + # Assert + self.assertIsNotNone(resp) + self.assertEqual(len(resp), 2) + self.assertIsNotNone(resp[0]) + self.assertNamedItemInContainer(resp, 'pref_file2') + self.assertNamedItemInContainer(resp, 'pref_dir3') + + def test_list_directories_and_files_with_prefix_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_list_directories_and_files_with_prefix_async()) + + async def _test_shared_access_share_async(self): + # SAS URL is calculated from storage key, so this test runs live only + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + file_name = 'file1' + dir_name = 'dir1' + data = b'hello world' + + share = await self._create_share() + dir1 = await share.create_directory(dir_name) + await dir1.upload_file(file_name, data) + + token = share.generate_shared_access_signature( + expiry=datetime.utcnow() + timedelta(hours=1), + permission=SharePermissions.READ, + ) + sas_client = FileClient( + self.get_file_url(), + share=share.share_name, + file_path=dir_name + '/' + file_name, + credential=token, + ) + + # Act + response = requests.get(sas_client.url) + + # Assert + self.assertTrue(response.ok) + self.assertEqual(data, response.content) + + def test_shared_access_share_async(self): + if TestMode.need_recording_file(self.test_mode): + return + loop = asyncio.get_event_loop() + loop.run_until_complete(self._test_shared_access_share_async()) + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + unittest.main() diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_queue_utils.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_queue_utils.py index 49086b696a39..74d433e0de56 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_queue_utils.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_queue_utils.py @@ -13,7 +13,7 @@ from azure.core.exceptions import ResourceExistsError, DecodeError from ._shared.models import StorageErrorCode -from ._shared.encryption import _decrypt_queue_message, _encrypt_queue_message +from ._shared.encryption import decrypt_queue_message, encrypt_queue_message from .models import QueueProperties @@ -58,7 +58,7 @@ def __call__(self, content): if content: content = self.encode(content) if self.key_encryption_key is not None: - content = _encrypt_queue_message(content, self.key_encryption_key) + content = encrypt_queue_message(content, self.key_encryption_key) return content def configure(self, require_encryption, key_encryption_key, resolver): @@ -85,7 +85,7 @@ def __call__(self, response, obj, headers): continue content = message.message_text if (self.key_encryption_key is not None) or (self.resolver is not None): - content = _decrypt_queue_message( + content = decrypt_queue_message( content, response, self.require_encryption, self.key_encryption_key, diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/__init__.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/__init__.py index 5b396cd202e8..160f88223820 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/__init__.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/__init__.py @@ -3,3 +3,54 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- + +import base64 +import hashlib +import hmac + +try: + from urllib.parse import quote, unquote +except ImportError: + from urllib2 import quote, unquote # type: ignore + +import six + + +def url_quote(url): + return quote(url) + + +def url_unquote(url): + return unquote(url) + + +def encode_base64(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + encoded = base64.b64encode(data) + return encoded.decode('utf-8') + + +def decode_base64_to_bytes(data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + return base64.b64decode(data) + + +def decode_base64_to_text(data): + decoded_bytes = decode_base64_to_bytes(data) + return decoded_bytes.decode('utf-8') + + +def sign_string(key, string_to_sign, key_is_base64=True): + if key_is_base64: + key = decode_base64_to_bytes(key) + else: + if isinstance(key, six.text_type): + key = key.encode('utf-8') + if isinstance(string_to_sign, six.text_type): + string_to_sign = string_to_sign.encode('utf-8') + signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) + digest = signed_hmac_sha256.digest() + encoded_digest = encode_base64(digest) + return encoded_digest diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/authentication.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/authentication.py index 4a2c4532d924..e9de0de09a94 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/authentication.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/authentication.py @@ -4,9 +4,6 @@ # license information. # -------------------------------------------------------------------------- -import base64 -import hashlib -import hmac import logging import sys try: @@ -18,43 +15,12 @@ from azure.core.exceptions import ClientAuthenticationError from azure.core.pipeline.policies import SansIOHTTPPolicy -if sys.version_info < (3,): - _unicode_type = unicode # pylint: disable=undefined-variable -else: - _unicode_type = str -logger = logging.getLogger(__name__) - - -def _encode_base64(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') +from . import sign_string -def _decode_base64_to_bytes(data): - if isinstance(data, _unicode_type): - data = data.encode('utf-8') - return base64.b64decode(data) - - -def _decode_base64_to_text(data): - decoded_bytes = _decode_base64_to_bytes(data) - return decoded_bytes.decode('utf-8') +logger = logging.getLogger(__name__) -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, _unicode_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, _unicode_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = _encode_base64(digest) - return encoded_digest # wraps a given exception with the desired exception type def _wrap_exception(ex, desired_type): @@ -125,7 +91,7 @@ def _get_canonicalized_resource_query(self, request): def _add_authorization_header(self, request, string_to_sign): try: - signature = _sign_string(self.account_key, string_to_sign) + signature = sign_string(self.account_key, string_to_sign) auth_string = 'SharedKey ' + self.account_name + ':' + signature request.http_request.headers['Authorization'] = auth_string except Exception as ex: diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py new file mode 100644 index 000000000000..2f6148afeb11 --- /dev/null +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/base_client.py @@ -0,0 +1,300 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging +try: + from urllib.parse import parse_qs +except ImportError: + from urlparse import parse_qs # type: ignore + +import six + +from azure.core import Configuration +from azure.core.pipeline import Pipeline +from azure.core.pipeline.transport import RequestsTransport +from azure.core.pipeline.policies import ( + RedirectPolicy, + ContentDecodePolicy, + BearerTokenCredentialPolicy, + ProxyPolicy) + +from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT +from .models import LocationMode +from .authentication import SharedKeyCredentialPolicy +from .shared_access_signature import QueryStringConstants +from .policies import ( + StorageHeadersPolicy, + StorageUserAgentPolicy, + StorageContentValidation, + StorageRequestHook, + StorageResponseHook, + StorageLoggingPolicy, + StorageHosts, + QueueMessagePolicy, + ExponentialRetry) + + +_LOGGER = logging.getLogger(__name__) +_SERVICE_PARAMS = { + 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, + 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, + 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, +} + + +class StorageAccountHostsMixin(object): + + def __init__( + self, parsed_url, # type: Any + service, # type: str + credential=None, # type: Optional[Any] + **kwargs # type: Any + ): + # type: (...) -> None + self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) + self._hosts = kwargs.get('_hosts') + self.scheme = parsed_url.scheme + + if service not in ['blob', 'queue', 'file']: + raise ValueError("Invalid service: {}".format(service)) + account = parsed_url.netloc.split(".{}.core.".format(service)) + secondary_hostname = None + self.credential = format_shared_key_credential(account, credential) + if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): + raise ValueError("Token credential is only supported with HTTPS.") + if hasattr(self.credential, 'account_name'): + secondary_hostname = "{}-secondary.{}.{}".format( + self.credential.account_name, service, SERVICE_HOST_BASE) + + if not self._hosts: + if len(account) > 1: + secondary_hostname = parsed_url.netloc.replace( + account[0], + account[0] + '-secondary') + if kwargs.get('secondary_hostname'): + secondary_hostname = kwargs['secondary_hostname'] + self._hosts = { + LocationMode.PRIMARY: parsed_url.netloc, + LocationMode.SECONDARY: secondary_hostname} + + self.require_encryption = kwargs.get('require_encryption', False) + self.key_encryption_key = kwargs.get('key_encryption_key') + self.key_resolver_function = kwargs.get('key_resolver_function') + + self._config, self._pipeline = create_pipeline( + self.credential, storage_sdk=service, hosts=self._hosts, **kwargs) + + def __enter__(self): + self._client.__enter__() + return self + + def __exit__(self, *args): + self._client.__exit__(*args) + + @property + def url(self): + return self._format_url(self._hosts[self._location_mode]) + + @property + def primary_endpoint(self): + return self._format_url(self._hosts[LocationMode.PRIMARY]) + + @property + def primary_hostname(self): + return self._hosts[LocationMode.PRIMARY] + + @property + def secondary_endpoint(self): + if not self._hosts[LocationMode.SECONDARY]: + raise ValueError("No secondary host configured.") + return self._format_url(self._hosts[LocationMode.SECONDARY]) + + @property + def secondary_hostname(self): + return self._hosts[LocationMode.SECONDARY] + + @property + def location_mode(self): + return self._location_mode + + @location_mode.setter + def location_mode(self, value): + if self._hosts.get(value): + self._location_mode = value + self._client._config.url = self.url # pylint: disable=protected-access + else: + raise ValueError("No host URL for location mode: {}".format(value)) + + def _format_query_string(self, sas_token, credential, snapshot=None, share_snapshot=None): + query_str = "?" + if snapshot: + query_str += 'snapshot={}&'.format(self.snapshot) + if share_snapshot: + query_str += 'sharesnapshot={}&'.format(self.snapshot) + if sas_token and not credential: + query_str += sas_token + elif is_credential_sastoken(credential): + query_str += credential.lstrip('?') + credential = None + return query_str.rstrip('?&'), credential + + +def format_shared_key_credential(account, credential): + if isinstance(credential, six.string_types): + if len(account) < 2: + raise ValueError("Unable to determine account name for shared key credential.") + credential = { + 'account_name': account[0], + 'account_key': credential + } + if isinstance(credential, dict): + if 'account_name' not in credential: + raise ValueError("Shared key credential missing 'account_name") + if 'account_key' not in credential: + raise ValueError("Shared key credential missing 'account_key") + return SharedKeyCredentialPolicy(**credential) + return credential + + +def parse_connection_str(conn_str, credential, service): + conn_str = conn_str.rstrip(';') + conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension + endpoints = _SERVICE_PARAMS[service] + primary = None + secondary = None + if not credential: + try: + credential = { + 'account_name': conn_settings['AccountName'], + 'account_key': conn_settings['AccountKey'] + } + except KeyError: + credential = conn_settings.get('SharedAccessSignature') + if endpoints['primary'] in conn_settings: + primary = conn_settings[endpoints['primary']] + if endpoints['secondary'] in conn_settings: + secondary = conn_settings[endpoints['secondary']] + else: + if endpoints['secondary'] in conn_settings: + raise ValueError("Connection string specifies only secondary endpoint.") + try: + primary = "{}://{}.{}.{}".format( + conn_settings['DefaultEndpointsProtocol'], + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + secondary = "{}-secondary.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings['EndpointSuffix'] + ) + except KeyError: + pass + + if not primary: + try: + primary = "https://{}.{}.{}".format( + conn_settings['AccountName'], + service, + conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) + ) + except KeyError: + raise ValueError("Connection string missing required connection details.") + return primary, secondary, credential + + +def create_configuration(**kwargs): + # type: (**Any) -> Configuration + if 'connection_timeout' not in kwargs: + kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT + config = Configuration(**kwargs) + config.headers_policy = StorageHeadersPolicy(**kwargs) + config.user_agent_policy = StorageUserAgentPolicy(**kwargs) + config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) + config.redirect_policy = RedirectPolicy(**kwargs) + config.logging_policy = StorageLoggingPolicy(**kwargs) + config.proxy_policy = ProxyPolicy(**kwargs) + + # Storage settings + config.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) + config.copy_polling_interval = 15 + + # Block blob uploads + config.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) + config.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) + config.use_byte_buffer = kwargs.get('use_byte_buffer', False) + + # Page blob uploads + config.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) + + # Blob downloads + config.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) + config.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) + + # File uploads + config.max_range_size = kwargs.get('max_range_size', 4 * 1024 * 1024) + return config + + +def create_pipeline(credential, **kwargs): + # type: (Any, **Any) -> Tuple[Configuration, Pipeline] + credential_policy = None + if hasattr(credential, 'get_token'): + credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) + elif isinstance(credential, SharedKeyCredentialPolicy): + credential_policy = credential + elif credential is not None: + raise TypeError("Unsupported credential: {}".format(credential)) + + config = kwargs.get('_configuration') or create_configuration(**kwargs) + if kwargs.get('_pipeline'): + return config, kwargs['_pipeline'] + transport = kwargs.get('transport') # type: HttpTransport + if not transport: + transport = RequestsTransport(config) + policies = [ + QueueMessagePolicy(), + config.headers_policy, + config.user_agent_policy, + StorageContentValidation(), + StorageRequestHook(**kwargs), + credential_policy, + ContentDecodePolicy(), + config.redirect_policy, + StorageHosts(**kwargs), + config.retry_policy, + config.logging_policy, + StorageResponseHook(**kwargs), + ] + return config, Pipeline(transport, policies=policies) + + +def parse_query(query_str): + sas_values = QueryStringConstants.to_list() + parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} + sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] + sas_token = None + if sas_params: + sas_token = '&'.join(sas_params) + + snapshot = parsed_query.get('snapshot') or parsed_query.get('sharesnapshot') + return snapshot, sas_token + + +def is_credential_sastoken(credential): + if not credential or not isinstance(credential, six.string_types): + return False + + sas_values = QueryStringConstants.to_list() + parsed_query = parse_qs(credential.lstrip('?')) + if parsed_query and all([k in sas_values for k in parsed_query.keys()]): + return True + return False diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/download_chunking.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/download_chunking.py index 41d6fc0dafea..e923a992e314 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/download_chunking.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/download_chunking.py @@ -3,18 +3,22 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- + +import sys import threading +from io import BytesIO from azure.core.exceptions import HttpResponseError from .models import ModifiedAccessConditions -from .utils import validate_and_format_range_headers, process_storage_error -from .encryption import _decrypt_blob +from .request_handlers import validate_and_format_range_headers +from .response_handlers import process_storage_error, parse_length_from_content_range +from .encryption import decrypt_blob -def process_range_and_offset(start_range, end_range, length, key_encryption_key, key_resolver_function): +def process_range_and_offset(start_range, end_range, length, encryption): start_offset, end_offset = 0, 0 - if key_encryption_key is not None or key_resolver_function is not None: + if encryption.get('key') is not None or encryption.get('resolver') is not None: if start_range is not None: # Align the start of the range along a 16 byte block start_offset = start_range % 16 @@ -35,68 +39,70 @@ def process_range_and_offset(start_range, end_range, length, key_encryption_key, return (start_range, end_range), (start_offset, end_offset) -def process_content(blob, start_offset, end_offset, require_encryption, key_encryption_key, key_resolver_function): - if key_encryption_key is not None or key_resolver_function is not None: +def process_content(data, start_offset, end_offset, encryption): + if encryption.get('key') is not None or encryption.get('resolver') is not None: try: - return _decrypt_blob( - require_encryption, - key_encryption_key, - key_resolver_function, - blob, + return decrypt_blob( + encryption.get('required'), + encryption.get('key'), + encryption.get('resolver'), + data, start_offset, end_offset) except Exception as error: raise HttpResponseError( message="Decryption failed.", - response=blob.response, + response=data.response, error=error) else: - return b"".join(list(blob)) + return b"".join(list(data)) -class _BlobChunkDownloader(object): # pylint: disable=too-many-instance-attributes +class _ChunkDownloader(object): def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, stream, - validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): - # identifiers for the blob - self.blob_service = blob_service + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + + self.service = service # information on the download range/chunk size self.chunk_size = chunk_size - self.download_size = download_size + self.total_size = total_size self.start_index = start_range - self.blob_end = end_range + self.end_index = end_range # the destination that we will write to self.stream = stream # download progress so far - self.progress_total = progress + self.progress_total = current_progress # encryption - self.require_encryption = require_encryption - self.key_encryption_key = key_encryption_key - self.key_resolver_function = key_resolver_function + self.encryption_options = encryption_options - # parameters for each get blob operation - self.timeout = timeout + # parameters for each get operation self.validate_content = validate_content - self.access_conditions = access_conditions - self.mod_conditions = mod_conditions self.request_options = kwargs def _calculate_range(self, chunk_start): - if chunk_start + self.chunk_size > self.blob_end: - chunk_end = self.blob_end + if chunk_start + self.chunk_size > self.end_index: + chunk_end = self.end_index else: chunk_end = chunk_start + self.chunk_size return chunk_start, chunk_end def get_chunk_offsets(self): index = self.start_index - while index < self.blob_end: + while index < self.end_index: yield index index += self.chunk_size @@ -122,57 +128,57 @@ def _write_to_stream(self, chunk_data, chunk_start): def _download_chunk(self, chunk_start, chunk_end): download_range, offset = process_range_and_offset( - chunk_start, - chunk_end, - chunk_end, - self.key_encryption_key, - self.key_resolver_function, - ) + chunk_start, chunk_end, chunk_end, self.encryption_options) range_header, range_validation = validate_and_format_range_headers( download_range[0], download_range[1] - 1, check_content_md5=self.validate_content) try: - _, response = self.blob_service.download( - timeout=self.timeout, + _, response = self.service.download( range=range_header, range_get_content_md5=range_validation, - lease_access_conditions=self.access_conditions, - modified_access_conditions=self.mod_conditions, validate_content=self.validate_content, - data_stream_total=self.download_size, + data_stream_total=self.total_size, download_stream_current=self.progress_total, **self.request_options) except HttpResponseError as error: process_storage_error(error) - chunk_data = process_content( - response, - offset[0], - offset[1], - self.require_encryption, - self.key_encryption_key, - self.key_resolver_function) + chunk_data = process_content(response, offset[0], offset[1], self.encryption_options) # This makes sure that if_match is set so that we can validate # that subsequent downloads are to an unmodified blob - if not self.mod_conditions: - self.mod_conditions = ModifiedAccessConditions() - self.mod_conditions.if_match = response.properties.etag + if self.request_options.get('modified_access_conditions'): + self.request_options['modified_access_conditions'].if_match = response.properties.etag + return chunk_data -class ParallelBlobChunkDownloader(_BlobChunkDownloader): - def __init__( - self, blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs): +class ParallelChunkDownloader(_ChunkDownloader): - super(ParallelBlobChunkDownloader, self).__init__( - blob_service, download_size, chunk_size, progress, start_range, end_range, - stream, validate_content, access_conditions, mod_conditions, timeout, - require_encryption, key_encryption_key, key_resolver_function, **kwargs) + def __init__( + self, service=None, + total_size=None, + chunk_size=None, + current_progress=None, + start_range=None, + end_range=None, + stream=None, + validate_content=None, + encryption_options=None, + **kwargs): + super(ParallelChunkDownloader, self).__init__( + service=service, + total_size=total_size, + chunk_size=chunk_size, + current_progress=current_progress, + start_range=start_range, + end_range=end_range, + stream=stream, + validate_content=validate_content, + encryption_options=encryption_options, + **kwargs) # for a parallel download, the stream is always seekable, so we note down the current position # in order to seek to the right place when out-of-order chunks come in @@ -193,7 +199,7 @@ def _write_to_stream(self, chunk_data, chunk_start): self.stream.write(chunk_data) -class SequentialBlobChunkDownloader(_BlobChunkDownloader): +class SequentialChunkDownloader(_ChunkDownloader): def _update_progress(self, length): self.progress_total += length @@ -201,3 +207,254 @@ def _update_progress(self, length): def _write_to_stream(self, chunk_data, chunk_start): # chunk_start is ignored in the case of sequential download since we cannot seek the destination stream self.stream.write(chunk_data) + + +class StorageStreamDownloader(object): + """A streaming object to download from Azure Storage. + + The stream downloader can iterated, or download to open file or stream + over multiple threads. + """ + + def __init__( + self, service=None, + config=None, + offset=None, + length=None, + validate_content=None, + encryption_options=None, + extra_properties=None, + **kwargs): + self.service = service + self.config = config + self.offset = offset + self.length = length + self.validate_content = validate_content + self.encryption_options = encryption_options or {} + self.request_options = kwargs + self.location_mode = None + self._download_complete = False + + # The service only provides transactional MD5s for chunks under 4MB. + # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first + # chunk so a transactional MD5 can be retrieved. + self.first_get_size = self.config.max_single_get_size if not self.validate_content \ + else self.config.max_chunk_get_size + initial_request_start = self.offset if self.offset is not None else 0 + if self.length is not None and self.length - self.offset < self.first_get_size: + initial_request_end = self.length + else: + initial_request_end = initial_request_start + self.first_get_size - 1 + + self.initial_range, self.initial_offset = process_range_and_offset( + initial_request_start, initial_request_end, self.length, self.encryption_options) + + self.download_size = None + self.file_size = None + self.response = self._initial_request() + self.properties = self.response.properties + + # Set the content length to the download size instead of the size of + # the last range + self.properties.size = self.download_size + + # Overwrite the content range to the user requested range + self.properties.content_range = 'bytes {0}-{1}/{2}'.format(self.offset, self.length, self.file_size) + + # Set additional properties according to download type + if extra_properties: + for prop, value in extra_properties.items(): + setattr(self.properties, prop, value) + + # Overwrite the content MD5 as it is the MD5 for the last range instead + # of the stored MD5 + # TODO: Set to the stored MD5 when the service returns this + self.properties.content_md5 = None + + def __len__(self): + return self.download_size + + def __iter__(self): + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + if content is not None: + yield content + if self._download_complete: + return + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader = SequentialBlobChunkDownloader( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + for chunk in downloader.get_chunk_offsets(): + yield downloader.yield_chunk(chunk) + + def _initial_request(self): + range_header, range_validation = validate_and_format_range_headers( + self.initial_range[0], + self.initial_range[1], + start_range_required=False, + end_range_required=False, + check_content_md5=self.validate_content) + + try: + location_mode, response = self.service.download( + range=range_header, + range_get_content_md5=range_validation, + validate_content=self.validate_content, + data_stream_total=None, + download_stream_current=0, + **self.request_options) + + # Check the location we read from to ensure we use the same one + # for subsequent requests. + self.location_mode = location_mode + + # Parse the total file size and adjust the download size if ranges + # were specified + self.file_size = parse_length_from_content_range(response.properties.content_range) + if self.length is not None: + # Use the length unless it is over the end of the file + self.download_size = min(self.file_size, self.length - self.offset + 1) + elif self.offset is not None: + self.download_size = self.file_size - self.offset + else: + self.download_size = self.file_size + + except HttpResponseError as error: + if self.offset is None and error.response.status_code == 416: + # Get range will fail on an empty file. If the user did not + # request a range, do a regular get request in order to get + # any properties. + try: + _, response = self.service.download( + validate_content=self.validate_content, + data_stream_total=0, + download_stream_current=0, + **self.request_options) + except HttpResponseError as error: + process_storage_error(error) + + # Set the download size to empty + self.download_size = 0 + self.file_size = 0 + else: + process_storage_error(error) + + # If the file is small, the download is complete at this point. + # If file size is large, download the rest of the file in chunks. + if response.properties.size != self.download_size: + # Lock on the etag. This can be overriden by the user by specifying '*' + if self.request_options.get('modified_access_conditions'): + if not self.request_options['modified_access_conditions'].if_match: + self.request_options['modified_access_conditions'].if_match = response.properties.etag + else: + self._download_complete = True + + return response + + + def content_as_bytes(self, max_connections=1): + """Download the contents of this file. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: bytes + """ + stream = BytesIO() + self.download_to_stream(stream, max_connections=max_connections) + return stream.getvalue() + + def content_as_text(self, max_connections=1, encoding='UTF-8'): + """Download the contents of this file, and decode as text. + + This operation is blocking until all data is downloaded. + + :param int max_connections: + The number of parallel connections with which to download. + :rtype: str + """ + content = self.content_as_bytes(max_connections=max_connections) + return content.decode(encoding) + + def download_to_stream(self, stream, max_connections=1): + """Download the contents of this file to a stream. + + :param stream: + The stream to download to. This can be an open file-handle, + or any writable stream. The stream must be seekable if the download + uses more than one parallel connection. + :returns: The properties of the downloaded file. + :rtype: Any + """ + # the stream must be seekable if parallel download is required + if max_connections > 1: + error_message = "Target stream handle must be seekable." + if sys.version_info >= (3,) and not stream.seekable(): + raise ValueError(error_message) + + try: + stream.seek(stream.tell()) + except (NotImplementedError, AttributeError): + raise ValueError(error_message) + + if self.download_size == 0: + content = b"" + else: + content = process_content( + self.response, self.initial_offset[0], self.initial_offset[1], self.encryption_options) + + # Write the content to the user stream + if content is not None: + stream.write(content) + if self._download_complete: + return self.properties + + data_end = self.file_size + if self.length is not None: + # Use the length unless it is over the end of the file + data_end = min(self.file_size, self.length + 1) + + downloader_class = ParallelChunkDownloader if max_connections > 1 else SequentialChunkDownloader + downloader = downloader_class( + service=self.service, + total_size=self.download_size, + chunk_size=self.config.max_chunk_get_size, + current_progress=self.first_get_size, + start_range=self.initial_range[1] + 1, # start where the first download ended + end_range=data_end, + stream=stream, + validate_content=self.validate_content, + encryption_options=self.encryption_options, + use_location=self.location_mode, + **self.request_options) + + if max_connections > 1: + import concurrent.futures + executor = concurrent.futures.ThreadPoolExecutor(max_connections) + list(executor.map(downloader.process_chunk, downloader.get_chunk_offsets())) + else: + for chunk in downloader.get_chunk_offsets(): + downloader.process_chunk(chunk) + + return self.properties diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/encryption.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/encryption.py index 222e213da627..b1178eaa9262 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/encryption.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/encryption.py @@ -21,22 +21,17 @@ from azure.core.exceptions import HttpResponseError from ..version import VERSION -from .authentication import _encode_base64, _decode_base64_to_bytes +from . import encode_base64, decode_base64_to_bytes _ENCRYPTION_PROTOCOL_V1 = '1.0' -_ERROR_VALUE_NONE = '{0} should not be None.' _ERROR_OBJECT_INVALID = \ '{0} does not define a complete interface. Value of {1} is either missing or invalid.' -_ERROR_DATA_NOT_ENCRYPTED = 'Encryption required, but received data does not contain appropriate metatadata.' + \ - 'Data was either not encrypted or metadata has been lost.' -_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM = \ - 'Specified encryption algorithm is not supported.' def _validate_not_none(param_name, param): if param is None: - raise ValueError(_ERROR_VALUE_NONE.format(param_name)) + raise ValueError('{0} should not be None.'.format(param_name)) def _validate_key_encryption_key_wrap(kek): @@ -147,7 +142,7 @@ def _generate_encryption_data_dict(kek, cek, iv): # Use OrderedDict to comply with Java's ordering requirement. wrapped_content_key = OrderedDict() wrapped_content_key['KeyId'] = kek.get_kid() - wrapped_content_key['EncryptedKey'] = _encode_base64(wrapped_cek) + wrapped_content_key['EncryptedKey'] = encode_base64(wrapped_cek) wrapped_content_key['Algorithm'] = kek.get_key_wrap_algorithm() encryption_agent = OrderedDict() @@ -157,7 +152,7 @@ def _generate_encryption_data_dict(kek, cek, iv): encryption_data_dict = OrderedDict() encryption_data_dict['WrappedContentKey'] = wrapped_content_key encryption_data_dict['EncryptionAgent'] = encryption_agent - encryption_data_dict['ContentEncryptionIV'] = _encode_base64(iv) + encryption_data_dict['ContentEncryptionIV'] = encode_base64(iv) encryption_data_dict['KeyWrappingMetadata'] = {'EncryptionLibrary': 'Python ' + VERSION} return encryption_data_dict @@ -180,7 +175,7 @@ def _dict_to_encryption_data(encryption_data_dict): raise ValueError("Unsupported encryption version.") wrapped_content_key = encryption_data_dict['WrappedContentKey'] wrapped_content_key = _WrappedContentKey(wrapped_content_key['Algorithm'], - _decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), + decode_base64_to_bytes(wrapped_content_key['EncryptedKey']), wrapped_content_key['KeyId']) encryption_agent = encryption_data_dict['EncryptionAgent'] @@ -192,7 +187,7 @@ def _dict_to_encryption_data(encryption_data_dict): else: key_wrapping_metadata = None - encryption_data = _EncryptionData(_decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), + encryption_data = _EncryptionData(decode_base64_to_bytes(encryption_data_dict['ContentEncryptionIV']), encryption_agent, wrapped_content_key, key_wrapping_metadata) @@ -259,7 +254,49 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol return content_encryption_key -def _encrypt_blob(blob, key_encryption_key): +def _decrypt_message(message, encryption_data, key_encryption_key=None, resolver=None): + ''' + Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. + Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). + Returns the original plaintex. + + :param str message: + The ciphertext to be decrypted. + :param _EncryptionData encryption_data: + The metadata associated with this ciphertext. + :param object key_encryption_key: + The user-provided key-encryption-key. Must implement the following methods: + unwrap_key(key, algorithm) + - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. + get_kid() + - returns a string key id for this key-encryption-key. + :param function resolver(kid): + The user-provided key resolver. Uses the kid string to return a key-encryption-key + implementing the interface defined above. + :return: The decrypted plaintext. + :rtype: str + ''' + _validate_not_none('message', message) + content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) + + if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: + raise ValueError('Specified encryption algorithm is not supported.') + + cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) + + # decrypt data + decrypted_data = message + decryptor = cipher.decryptor() + decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) + + # unpad data + unpadder = PKCS7(128).unpadder() + decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) + + return decrypted_data + + +def encrypt_blob(blob, key_encryption_key): ''' Encrypts the given blob using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -302,7 +339,7 @@ def _encrypt_blob(blob, key_encryption_key): return dumps(encryption_data), encrypted_data -def _generate_blob_encryption_data(key_encryption_key): +def generate_blob_encryption_data(key_encryption_key): ''' Generates the encryption_metadata for the blob. @@ -328,7 +365,7 @@ def _generate_blob_encryption_data(key_encryption_key): return content_encryption_key, initialization_vector, encryption_data -def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, +def decrypt_blob(require_encryption, key_encryption_key, key_resolver, response, start_offset, end_offset): ''' Decrypts the given blob contents and returns only the requested range. @@ -356,12 +393,14 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, encryption_data = _dict_to_encryption_data(loads(response.response.headers['x-ms-meta-encryptiondata'])) except: # pylint: disable=bare-except if require_encryption: - raise ValueError(_ERROR_DATA_NOT_ENCRYPTED) + raise ValueError( + 'Encryption required, but received data does not contain appropriate metatadata.' + \ + 'Data was either not encrypted or metadata has been lost.') return content if encryption_data.encryption_agent.encryption_algorithm != _EncryptionAlgorithm.AES_CBC_256: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) + raise ValueError('Specified encryption algorithm is not supported.') blob_type = response.response.headers['x-ms-blob-type'] @@ -407,7 +446,7 @@ def _decrypt_blob(require_encryption, key_encryption_key, key_resolver, return content[start_offset: len(content) - end_offset] -def _get_blob_encryptor_and_padder(cek, iv, should_pad): +def get_blob_encryptor_and_padder(cek, iv, should_pad): encryptor = None padder = None @@ -419,7 +458,7 @@ def _get_blob_encryptor_and_padder(cek, iv, should_pad): return encryptor, padder -def _encrypt_queue_message(message, key_encryption_key): +def encrypt_queue_message(message, key_encryption_key): ''' Encrypts the given plain text message using AES256 in CBC mode with 128 bit padding. Wraps the generated content-encryption-key using the user-provided key-encryption-key (kek). @@ -459,7 +498,7 @@ def _encrypt_queue_message(message, key_encryption_key): encrypted_data = encryptor.update(padded_data) + encryptor.finalize() # Build the dictionary structure. - queue_message = {'EncryptedMessageContents': _encode_base64(encrypted_data), + queue_message = {'EncryptedMessageContents': encode_base64(encrypted_data), 'EncryptionData': _generate_encryption_data_dict(key_encryption_key, content_encryption_key, initialization_vector)} @@ -467,7 +506,7 @@ def _encrypt_queue_message(message, key_encryption_key): return dumps(queue_message) -def _decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): +def decrypt_queue_message(message, response, require_encryption, key_encryption_key, resolver): ''' Returns the decrypted message contents from an EncryptedQueueMessage. If no encryption metadata is present, will return the unaltered message. @@ -492,7 +531,7 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption message = loads(message) encryption_data = _dict_to_encryption_data(message['EncryptionData']) - decoded_data = _decode_base64_to_bytes(message['EncryptedMessageContents']) + decoded_data = decode_base64_to_bytes(message['EncryptedMessageContents']) except (KeyError, ValueError): # Message was not json formatted and so was not encrypted # or the user provided a json formatted message. @@ -501,51 +540,9 @@ def _decrypt_queue_message(message, response, require_encryption, key_encryption return message try: - return _decrypt(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') + return _decrypt_message(decoded_data, encryption_data, key_encryption_key, resolver).decode('utf-8') except Exception as error: raise HttpResponseError( message="Decryption failed.", response=response, error=error) - - -def _decrypt(message, encryption_data, key_encryption_key=None, resolver=None): - ''' - Decrypts the given ciphertext using AES256 in CBC mode with 128 bit padding. - Unwraps the content-encryption-key using the user-provided or resolved key-encryption-key (kek). - Returns the original plaintex. - - :param str message: - The ciphertext to be decrypted. - :param _EncryptionData encryption_data: - The metadata associated with this ciphertext. - :param object key_encryption_key: - The user-provided key-encryption-key. Must implement the following methods: - unwrap_key(key, algorithm) - - returns the unwrapped form of the specified symmetric key using the string-specified algorithm. - get_kid() - - returns a string key id for this key-encryption-key. - :param function resolver(kid): - The user-provided key resolver. Uses the kid string to return a key-encryption-key - implementing the interface defined above. - :return: The decrypted plaintext. - :rtype: str - ''' - _validate_not_none('message', message) - content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, resolver) - - if _EncryptionAlgorithm.AES_CBC_256 != encryption_data.encryption_agent.encryption_algorithm: - raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM) - - cipher = _generate_AES_CBC_cipher(content_encryption_key, encryption_data.content_encryption_IV) - - # decrypt data - decrypted_data = message - decryptor = cipher.decryptor() - decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) - - # unpad data - unpadder = PKCS7(128).unpadder() - decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize()) - - return decrypted_data 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 dbad2a1c58c8..30e4506254d6 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 @@ -138,6 +138,26 @@ class StorageErrorCode(str, Enum): queue_not_empty = "QueueNotEmpty" queue_not_found = "QueueNotFound" + # File values + cannot_delete_file_or_directory = "CannotDeleteFileOrDirectory" + client_cache_flush_delay = "ClientCacheFlushDelay" + delete_pending = "DeletePending" + directory_not_empty = "DirectoryNotEmpty" + file_lock_conflict = "FileLockConflict" + invalid_file_or_directory_path_name = "InvalidFileOrDirectoryPathName" + parent_not_found = "ParentNotFound" + read_only_attribute = "ReadOnlyAttribute" + share_already_exists = "ShareAlreadyExists" + share_being_deleted = "ShareBeingDeleted" + share_disabled = "ShareDisabled" + share_not_found = "ShareNotFound" + sharing_violation = "SharingViolation" + share_snapshot_in_progress = "ShareSnapshotInProgress" + share_snapshot_count_exceeded = "ShareSnapshotCountExceeded" + share_snapshot_operation_not_supported = "ShareSnapshotOperationNotSupported" + share_has_snapshots = "ShareHasSnapshots" + container_quota_downgrade_not_allowed = "ContainerQuotaDowngradeNotAllowed" + class DictMixin(object): diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py index f7736256bc4b..5b0212fd9090 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/policies.py @@ -47,11 +47,13 @@ except NameError: _unicode_type = str - -_LOGGER = logging.getLogger(__name__) if TYPE_CHECKING: from azure.core.pipeline import PipelineRequest, PipelineResponse + +_LOGGER = logging.getLogger(__name__) + + def encode_base64(data): if isinstance(data, _unicode_type): data = data.encode('utf-8') @@ -102,25 +104,6 @@ def on_request(self, request, **kwargs): message_id) -class StorageBlobSettings(object): - - def __init__(self, **kwargs): - self.max_single_put_size = kwargs.get('max_single_put_size', 64 * 1024 * 1024) - self.copy_polling_interval = 15 - - # Block blob uploads - self.max_block_size = kwargs.get('max_block_size', 4 * 1024 * 1024) - self.min_large_block_upload_threshold = kwargs.get('min_large_block_upload_threshold', 4 * 1024 * 1024 + 1) - self.use_byte_buffer = kwargs.get('use_byte_buffer', False) - - # Page blob uploads - self.max_page_size = kwargs.get('max_page_size', 4 * 1024 * 1024) - - # Blob downloads - self.max_single_get_size = kwargs.get('max_single_get_size', 32 * 1024 * 1024) - self.max_chunk_get_size = kwargs.get('max_chunk_get_size', 4 * 1024 * 1024) - - class StorageHeadersPolicy(HeadersPolicy): def on_request(self, request, **kwargs): @@ -251,7 +234,9 @@ class StorageUserAgentPolicy(SansIOHTTPPolicy): def __init__(self, **kwargs): self._application = kwargs.pop('user_agent', None) - self._user_agent = "azsdk-python-storage-queue/{} Python/{} ({})".format( + storage_sdk = kwargs.pop('storage_sdk') + self._user_agent = "azsdk-python-storage-{}/{} Python/{} ({})".format( + storage_sdk, VERSION, platform.python_version(), platform.platform()) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/request_handlers.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/request_handlers.py new file mode 100644 index 000000000000..cd5e4848633d --- /dev/null +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/request_handlers.py @@ -0,0 +1,144 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) + +import logging +from os import fstat +from io import (SEEK_END, SEEK_SET, UnsupportedOperation) + +import isodate + +from azure.core import Configuration +from azure.core.exceptions import raise_with_traceback +from azure.core.pipeline import Pipeline + + +_LOGGER = logging.getLogger(__name__) + + +def serialize_iso(attr): + """Serialize Datetime object into ISO-8601 formatted string. + + :param Datetime attr: Object to be serialized. + :rtype: str + :raises: ValueError if format invalid. + """ + if not attr: + return None + if isinstance(attr, str): + attr = isodate.parse_datetime(attr) + try: + utc = attr.utctimetuple() + if utc.tm_year > 9999 or utc.tm_year < 1: + raise OverflowError("Hit max or min date") + + date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( + utc.tm_year, utc.tm_mon, utc.tm_mday, + utc.tm_hour, utc.tm_min, utc.tm_sec) + return date + 'Z' + except (ValueError, OverflowError) as err: + msg = "Unable to serialize datetime object." + raise_with_traceback(ValueError, msg, err) + except AttributeError as err: + msg = "ISO-8601 object must be valid Datetime object." + raise_with_traceback(TypeError, msg, err) + + +def get_length(data): + length = None + # Check if object implements the __len__ method, covers most input cases such as bytearray. + try: + length = len(data) + except: # pylint: disable=bare-except + pass + + if not length: + # Check if the stream is a file-like stream object. + # If so, calculate the size using the file descriptor. + try: + fileno = data.fileno() + except (AttributeError, UnsupportedOperation): + pass + else: + return fstat(fileno).st_size + + # If the stream is seekable and tell() is implemented, calculate the stream size. + try: + current_position = data.tell() + data.seek(0, SEEK_END) + length = data.tell() - current_position + data.seek(current_position, SEEK_SET) + except (AttributeError, UnsupportedOperation): + pass + + return length + + +def read_length(data): + try: + if hasattr(data, 'read'): + read_data = b'' + for chunk in iter(lambda: data.read(4096), b""): + read_data += chunk + return len(read_data), read_data + if hasattr(data, '__iter__'): + read_data = b'' + for chunk in data: + read_data += chunk + return len(read_data), read_data + except: # pylint: disable=bare-except + pass + raise ValueError("Unable to calculate content length, please specify.") + + +def validate_and_format_range_headers( + start_range, end_range, start_range_required=True, + end_range_required=True, check_content_md5=False, align_to_page=False): + # If end range is provided, start range must be provided + if (start_range_required or end_range is not None) and start_range is None: + raise ValueError("start_range value cannot be None.") + if end_range_required and end_range is None: + raise ValueError("end_range value cannot be None.") + + # Page ranges must be 512 aligned + if align_to_page: + if start_range is not None and start_range % 512 != 0: + raise ValueError("Invalid page blob start_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(start_range)) + if end_range is not None and end_range % 512 != 511: + raise ValueError("Invalid page blob end_range: {0}. " + "The size must be aligned to a 512-byte boundary.".format(end_range)) + + # Format based on whether end_range is present + range_header = None + if end_range is not None: + range_header = 'bytes={0}-{1}'.format(start_range, end_range) + elif start_range is not None: + range_header = "bytes={0}-".format(start_range) + + # Content MD5 can only be provided for a complete range less than 4MB in size + range_validation = None + if check_content_md5: + if start_range is None or end_range is None: + raise ValueError("Both start and end range requied for MD5 content validation.") + if end_range - start_range > 4 * 1024 * 1024: + raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") + range_validation = 'true' + + return range_header, range_validation + + +def add_metadata_headers(metadata=None): + # type: (Optional[Dict[str, str]]) -> Dict[str, str] + headers = {} + if metadata: + for key, value in metadata.items(): + headers['x-ms-meta-{}'.format(key)] = value + return headers 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 new file mode 100644 index 000000000000..472399264aa9 --- /dev/null +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/response_handlers.py @@ -0,0 +1,132 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +from typing import ( # pylint: disable=unused-import + Union, Optional, Any, Iterable, Dict, List, Type, Tuple, + TYPE_CHECKING +) +import logging + +from azure.core.pipeline.policies import ContentDecodePolicy +from azure.core.exceptions import ( + HttpResponseError, + ResourceNotFoundError, + ResourceModifiedError, + ResourceExistsError, + ClientAuthenticationError, + DecodeError) + +from .models import StorageErrorCode + + +if TYPE_CHECKING: + from datetime import datetime + from azure.core.exceptions import AzureError + + +_LOGGER = logging.getLogger(__name__) + + +def parse_length_from_content_range(content_range): + ''' + Parses the blob length from the content range header: bytes 1-3/65537 + ''' + if content_range is None: + return None + + # First, split in space and take the second half: '1-3/65537' + # Next, split on slash and take the second half: '65537' + # Finally, convert to an int: 65537 + return int(content_range.split(' ', 1)[1].split('/', 1)[1]) + + +def normalize_headers(headers): + normalized = {} + for key, value in headers.items(): + if key.startswith('x-ms-'): + key = key[5:] + normalized[key.lower().replace('-', '_')] = value + return normalized + + +def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argument + raw_metadata = {k: v for k, v in response.headers.items() if k.startswith("x-ms-meta-")} + return {k[10:]: v for k, v in raw_metadata.items()} + + +def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers) + + +def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return normalize_headers(response_headers), deserialized + + +def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument + return response.location_mode, deserialized + + +def process_storage_error(storage_error): + raise_error = HttpResponseError + error_code = storage_error.response.headers.get('x-ms-error-code') + error_message = storage_error.message + additional_data = {} + try: + error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) + if error_body: + for info in error_body.iter(): + if info.tag.lower() == 'code': + error_code = info.text + elif info.tag.lower() == 'message': + error_message = info.text + else: + additional_data[info.tag] = info.text + except DecodeError: + pass + + try: + if error_code: + error_code = StorageErrorCode(error_code) + if error_code in [StorageErrorCode.condition_not_met, + StorageErrorCode.blob_overwritten]: + raise_error = ResourceModifiedError + if error_code in [StorageErrorCode.invalid_authentication_info, + StorageErrorCode.authentication_failed]: + raise_error = ClientAuthenticationError + if error_code in [StorageErrorCode.resource_not_found, + StorageErrorCode.blob_not_found, + StorageErrorCode.queue_not_found, + StorageErrorCode.container_not_found, + StorageErrorCode.parent_not_found, + StorageErrorCode.share_not_found]: + raise_error = ResourceNotFoundError + if error_code in [StorageErrorCode.account_already_exists, + StorageErrorCode.account_being_created, + StorageErrorCode.resource_already_exists, + StorageErrorCode.resource_type_mismatch, + StorageErrorCode.blob_already_exists, + StorageErrorCode.queue_already_exists, + StorageErrorCode.container_already_exists, + StorageErrorCode.container_being_deleted, + StorageErrorCode.queue_being_deleted, + StorageErrorCode.share_already_exists, + StorageErrorCode.share_being_deleted]: + raise_error = ResourceExistsError + except ValueError: + # Got an unknown error code + pass + + try: + error_message += "\nErrorCode:{}".format(error_code.value) + except AttributeError: + error_message += "\nErrorCode:{}".format(error_code) + for name, info in additional_data.items(): + error_message += "\n{}:{}".format(name, info) + + error = raise_error(message=error_message, response=storage_error.response) + error.error_code = error_code + error.additional_info = additional_data + raise error diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/shared_access_signature.py index cad3f270600b..16ff778c5c1e 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 @@ -8,7 +8,7 @@ from datetime import date from .constants import X_MS_VERSION -from .utils import _sign_string, url_quote, _QueryStringConstants +from . import sign_string, url_quote if sys.version_info < (3,): @@ -25,6 +25,54 @@ def _to_utc_datetime(value): return value.strftime('%Y-%m-%dT%H:%M:%SZ') +class QueryStringConstants(object): + SIGNED_SIGNATURE = 'sig' + SIGNED_PERMISSION = 'sp' + SIGNED_START = 'st' + SIGNED_EXPIRY = 'se' + SIGNED_RESOURCE = 'sr' + SIGNED_IDENTIFIER = 'si' + SIGNED_IP = 'sip' + SIGNED_PROTOCOL = 'spr' + SIGNED_VERSION = 'sv' + SIGNED_CACHE_CONTROL = 'rscc' + SIGNED_CONTENT_DISPOSITION = 'rscd' + SIGNED_CONTENT_ENCODING = 'rsce' + SIGNED_CONTENT_LANGUAGE = 'rscl' + SIGNED_CONTENT_TYPE = 'rsct' + START_PK = 'spk' + START_RK = 'srk' + END_PK = 'epk' + END_RK = 'erk' + SIGNED_RESOURCE_TYPES = 'srt' + SIGNED_SERVICES = 'ss' + + @staticmethod + def to_list(): + return [ + QueryStringConstants.SIGNED_SIGNATURE, + QueryStringConstants.SIGNED_PERMISSION, + QueryStringConstants.SIGNED_START, + QueryStringConstants.SIGNED_EXPIRY, + QueryStringConstants.SIGNED_RESOURCE, + QueryStringConstants.SIGNED_IDENTIFIER, + QueryStringConstants.SIGNED_IP, + QueryStringConstants.SIGNED_PROTOCOL, + QueryStringConstants.SIGNED_VERSION, + QueryStringConstants.SIGNED_CACHE_CONTROL, + QueryStringConstants.SIGNED_CONTENT_DISPOSITION, + QueryStringConstants.SIGNED_CONTENT_ENCODING, + QueryStringConstants.SIGNED_CONTENT_LANGUAGE, + QueryStringConstants.SIGNED_CONTENT_TYPE, + QueryStringConstants.START_PK, + QueryStringConstants.START_RK, + QueryStringConstants.END_PK, + QueryStringConstants.END_RK, + QueryStringConstants.SIGNED_RESOURCE_TYPES, + QueryStringConstants.SIGNED_SERVICES, + ] + + class SharedAccessSignature(object): ''' Provides a factory for creating account access @@ -112,33 +160,33 @@ def add_base(self, permission, expiry, start, ip, protocol, x_ms_version): 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) + 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) + self._add_query(QueryStringConstants.SIGNED_RESOURCE, resource) def add_id(self, policy_id): - self._add_query(_QueryStringConstants.SIGNED_IDENTIFIER, 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) + 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) + 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): @@ -153,29 +201,29 @@ def get_value_to_append(query): # 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) + + (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_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)) + (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)) + 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): @@ -184,17 +232,17 @@ def get_value_to_append(query): 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)) + 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]) @@ -451,18 +499,193 @@ def get_value_to_append(query): # 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) + + (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_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)) + 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/_shared/upload_chunking.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/upload_chunking.py index 775c56853eac..86a3f4224ffa 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/upload_chunking.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/upload_chunking.py @@ -13,23 +13,45 @@ import six from .models import ModifiedAccessConditions -from .utils import ( - encode_base64, - url_quote, - get_length, - return_response_headers) -from .encryption import _get_blob_encryptor_and_padder +from . import encode_base64, url_quote +from .request_handlers import get_length +from .response_handlers import return_response_headers +from .encryption import get_blob_encryptor_and_padder _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024 _ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.' +def upload_file_chunks(file_service, file_size, block_size, stream, max_connections, + validate_content, timeout, **kwargs): + uploader = FileChunkUploader( + file_service, + file_size, + block_size, + stream, + max_connections > 1, + validate_content, + timeout, + **kwargs + ) + if max_connections > 1: + import concurrent.futures + executor = concurrent.futures.ThreadPoolExecutor(max_connections) + range_ids = list(executor.map(uploader.process_chunk, uploader.get_chunk_offsets())) + else: + if file_size is not None: + range_ids = [uploader.process_chunk(start) for start in uploader.get_chunk_offsets()] + else: + range_ids = uploader.process_all_unknown_size() + return range_ids + + def upload_blob_chunks(blob_service, blob_size, block_size, stream, max_connections, validate_content, # pylint: disable=too-many-locals access_conditions, uploader_class, append_conditions=None, modified_access_conditions=None, timeout=None, content_encryption_key=None, initialization_vector=None, **kwargs): - encryptor, padder = _get_blob_encryptor_and_padder( + encryptor, padder = get_blob_encryptor_and_padder( content_encryption_key, initialization_vector, uploader_class is not PageBlobChunkUploader) @@ -353,6 +375,94 @@ def _upload_chunk(self, chunk_offset, chunk_data): ) +class FileChunkUploader(object): # pylint: disable=too-many-instance-attributes + + def __init__(self, file_service, file_size, chunk_size, stream, parallel, + validate_content, timeout, **kwargs): + self.file_service = file_service + self.file_size = file_size + self.chunk_size = chunk_size + self.stream = stream + self.parallel = parallel + self.stream_start = stream.tell() if parallel else None + self.stream_lock = Lock() if parallel else None + self.progress_total = 0 + self.progress_lock = Lock() if parallel else None + self.validate_content = validate_content + self.timeout = timeout + self.request_options = kwargs + + def get_chunk_offsets(self): + index = 0 + if self.file_size is None: + # we don't know the size of the stream, so we have no + # choice but to seek + while True: + data = self._read_from_stream(index, 1) + if not data: + break + yield index + index += self.chunk_size + else: + while index < self.file_size: + yield index + index += self.chunk_size + + def process_chunk(self, chunk_offset): + size = self.chunk_size + if self.file_size is not None: + size = min(size, self.file_size - chunk_offset) + chunk_data = self._read_from_stream(chunk_offset, size) + return self._upload_chunk_with_progress(chunk_offset, chunk_data) + + def process_all_unknown_size(self): + assert self.stream_lock is None + range_ids = [] + index = 0 + while True: + data = self._read_from_stream(None, self.chunk_size) + if data: + index += len(data) + range_id = self._upload_chunk_with_progress(index, data) + range_ids.append(range_id) + else: + break + + return range_ids + + def _read_from_stream(self, offset, count): + if self.stream_lock is not None: + with self.stream_lock: + self.stream.seek(self.stream_start + offset) + data = self.stream.read(count) + else: + data = self.stream.read(count) + return data + + def _update_progress(self, length): + if self.progress_lock is not None: + with self.progress_lock: + self.progress_total += length + else: + self.progress_total += length + + def _upload_chunk_with_progress(self, chunk_start, chunk_data): + chunk_end = chunk_start + len(chunk_data) - 1 + self.file_service.upload_range( + chunk_data, + chunk_start, + chunk_end, + validate_content=self.validate_content, + timeout=self.timeout, + data_stream_total=self.file_size, + upload_stream_current=self.progress_total, + **self.request_options + ) + range_id = 'bytes={0}-{1}'.format(chunk_start, chunk_end) + self._update_progress(len(chunk_data)) + return range_id + + class _SubStream(IOBase): def __init__(self, wrapped_stream, stream_begin_index, length, lockObj): # Python 2.7: file-like objects created with open() typically support seek(), but are not diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/utils.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/utils.py deleted file mode 100644 index 6d980d967891..000000000000 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/utils.py +++ /dev/null @@ -1,606 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - -from typing import ( # pylint: disable=unused-import - Union, Optional, Any, Iterable, Dict, List, Type, Tuple, - TYPE_CHECKING -) -import base64 -import hashlib -import hmac -import logging -from os import fstat -from io import (SEEK_END, SEEK_SET, UnsupportedOperation) - -try: - from urllib.parse import quote, unquote, parse_qs -except ImportError: - from urlparse import parse_qs # type: ignore - from urllib2 import quote, unquote # type: ignore - -import six -import isodate - -from azure.core import Configuration -from azure.core.exceptions import raise_with_traceback -from azure.core.pipeline import Pipeline -from azure.core.pipeline.transport import RequestsTransport -from azure.core.pipeline.policies import ( - RedirectPolicy, - ContentDecodePolicy, - BearerTokenCredentialPolicy, - ProxyPolicy) -from azure.core.exceptions import ( - HttpResponseError, - ResourceNotFoundError, - ResourceModifiedError, - ResourceExistsError, - ClientAuthenticationError, - DecodeError) - -from .constants import STORAGE_OAUTH_SCOPE, SERVICE_HOST_BASE, DEFAULT_SOCKET_TIMEOUT -from .models import LocationMode, StorageErrorCode -from .authentication import SharedKeyCredentialPolicy -from .policies import ( - StorageBlobSettings, - StorageHeadersPolicy, - StorageUserAgentPolicy, - StorageContentValidation, - StorageRequestHook, - StorageResponseHook, - StorageLoggingPolicy, - StorageHosts, - QueueMessagePolicy, - ExponentialRetry) - - -if TYPE_CHECKING: - from datetime import datetime - from azure.core.pipeline.transport import HttpTransport - from azure.core.pipeline.policies import HTTPPolicy - from azure.core.exceptions import AzureError - - -_LOGGER = logging.getLogger(__name__) - - -class _QueryStringConstants(object): - SIGNED_SIGNATURE = 'sig' - SIGNED_PERMISSION = 'sp' - SIGNED_START = 'st' - SIGNED_EXPIRY = 'se' - SIGNED_RESOURCE = 'sr' - SIGNED_IDENTIFIER = 'si' - SIGNED_IP = 'sip' - SIGNED_PROTOCOL = 'spr' - SIGNED_VERSION = 'sv' - SIGNED_CACHE_CONTROL = 'rscc' - SIGNED_CONTENT_DISPOSITION = 'rscd' - SIGNED_CONTENT_ENCODING = 'rsce' - SIGNED_CONTENT_LANGUAGE = 'rscl' - SIGNED_CONTENT_TYPE = 'rsct' - START_PK = 'spk' - START_RK = 'srk' - END_PK = 'epk' - END_RK = 'erk' - SIGNED_RESOURCE_TYPES = 'srt' - SIGNED_SERVICES = 'ss' - - @staticmethod - def to_list(): - return [ - _QueryStringConstants.SIGNED_SIGNATURE, - _QueryStringConstants.SIGNED_PERMISSION, - _QueryStringConstants.SIGNED_START, - _QueryStringConstants.SIGNED_EXPIRY, - _QueryStringConstants.SIGNED_RESOURCE, - _QueryStringConstants.SIGNED_IDENTIFIER, - _QueryStringConstants.SIGNED_IP, - _QueryStringConstants.SIGNED_PROTOCOL, - _QueryStringConstants.SIGNED_VERSION, - _QueryStringConstants.SIGNED_CACHE_CONTROL, - _QueryStringConstants.SIGNED_CONTENT_DISPOSITION, - _QueryStringConstants.SIGNED_CONTENT_ENCODING, - _QueryStringConstants.SIGNED_CONTENT_LANGUAGE, - _QueryStringConstants.SIGNED_CONTENT_TYPE, - _QueryStringConstants.START_PK, - _QueryStringConstants.START_RK, - _QueryStringConstants.END_PK, - _QueryStringConstants.END_RK, - _QueryStringConstants.SIGNED_RESOURCE_TYPES, - _QueryStringConstants.SIGNED_SERVICES, - ] - - -class StorageAccountHostsMixin(object): - - def __init__( - self, parsed_url, # type: Any - service, # type: str - credential=None, # type: Optional[Any] - **kwargs # type: Any - ): - # type: (...) -> None - self._location_mode = kwargs.get('_location_mode', LocationMode.PRIMARY) - self._hosts = kwargs.get('_hosts') - self.scheme = parsed_url.scheme - - if service not in ['blob', 'queue', 'file']: - raise ValueError("Invalid service: {}".format(service)) - account = parsed_url.netloc.split(".{}.core.".format(service)) - secondary_hostname = None - self.credential = format_shared_key_credential(account, credential) - if self.scheme.lower() != 'https' and hasattr(self.credential, 'get_token'): - raise ValueError("Token credential is only supported with HTTPS.") - if hasattr(self.credential, 'account_name'): - secondary_hostname = "{}-secondary.{}.{}".format( - self.credential.account_name, service, SERVICE_HOST_BASE) - - if not self._hosts: - if len(account) > 1: - secondary_hostname = parsed_url.netloc.replace( - account[0], - account[0] + '-secondary') - if kwargs.get('secondary_hostname'): - secondary_hostname = kwargs['secondary_hostname'] - self._hosts = { - LocationMode.PRIMARY: parsed_url.netloc, - LocationMode.SECONDARY: secondary_hostname} - - self.require_encryption = kwargs.get('require_encryption', False) - self.key_encryption_key = kwargs.get('key_encryption_key') - self.key_resolver_function = kwargs.get('key_resolver_function') - - self._config, self._pipeline = create_pipeline(self.credential, hosts=self._hosts, **kwargs) - - def __enter__(self): - self._client.__enter__() - return self - - def __exit__(self, *args): - self._client.__exit__(*args) - - @property - def url(self): - return self._format_url(self._hosts[self._location_mode]) - - @property - def primary_endpoint(self): - return self._format_url(self._hosts[LocationMode.PRIMARY]) - - @property - def primary_hostname(self): - return self._hosts[LocationMode.PRIMARY] - - @property - def secondary_endpoint(self): - if not self._hosts[LocationMode.SECONDARY]: - raise ValueError("No secondary host configured.") - return self._format_url(self._hosts[LocationMode.SECONDARY]) - - @property - def secondary_hostname(self): - return self._hosts[LocationMode.SECONDARY] - - @property - def location_mode(self): - return self._location_mode - - @location_mode.setter - def location_mode(self, value): - if self._hosts.get(value): - self._location_mode = value - self._client._config.url = self.url # pylint: disable=protected-access - else: - raise ValueError("No host URL for location mode: {}".format(value)) - - def _format_query_string(self, sas_token, credential, snapshot=None): - query_str = "?" - if snapshot: - query_str += 'snapshot={}&'.format(self.snapshot) - if sas_token and not credential: - query_str += sas_token - elif is_credential_sastoken(credential): - query_str += credential.lstrip('?') - credential = None - return query_str.rstrip('?&'), credential - - -def format_shared_key_credential(account, credential): - if isinstance(credential, six.string_types): - if len(account) < 2: - raise ValueError("Unable to determine account name for shared key credential.") - credential = { - 'account_name': account[0], - 'account_key': credential - } - if isinstance(credential, dict): - if 'account_name' not in credential: - raise ValueError("Shared key credential missing 'account_name") - if 'account_key' not in credential: - raise ValueError("Shared key credential missing 'account_key") - return SharedKeyCredentialPolicy(**credential) - return credential - - -service_connection_params = { - 'blob': {'primary': 'BlobEndpoint', 'secondary': 'BlobSecondaryEndpoint'}, - 'queue': {'primary': 'QueueEndpoint', 'secondary': 'QueueSecondaryEndpoint'}, - 'file': {'primary': 'FileEndpoint', 'secondary': 'FileSecondaryEndpoint'}, -} - - -def parse_connection_str(conn_str, credential, service): - conn_str = conn_str.rstrip(';') - conn_settings = dict([s.split('=', 1) for s in conn_str.split(';')]) # pylint: disable=consider-using-dict-comprehension - endpoints = service_connection_params[service] - primary = None - secondary = None - if not credential: - try: - credential = { - 'account_name': conn_settings['AccountName'], - 'account_key': conn_settings['AccountKey'] - } - except KeyError: - credential = conn_settings.get('SharedAccessSignature') - if endpoints['primary'] in conn_settings: - primary = conn_settings[endpoints['primary']] - if endpoints['secondary'] in conn_settings: - secondary = conn_settings[endpoints['secondary']] - else: - if endpoints['secondary'] in conn_settings: - raise ValueError("Connection string specifies only secondary endpoint.") - try: - primary = "{}://{}.{}.{}".format( - conn_settings['DefaultEndpointsProtocol'], - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - secondary = "{}-secondary.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings['EndpointSuffix'] - ) - except KeyError: - pass - - if not primary: - try: - primary = "https://{}.{}.{}".format( - conn_settings['AccountName'], - service, - conn_settings.get('EndpointSuffix', SERVICE_HOST_BASE) - ) - except KeyError: - raise ValueError("Connection string missing required connection details.") - return primary, secondary, credential - - -def url_quote(url): - return quote(url) - - -def url_unquote(url): - return unquote(url) - - -def encode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - encoded = base64.b64encode(data) - return encoded.decode('utf-8') - - -def decode_base64(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - decoded = base64.b64decode(data) - return decoded.decode('utf-8') - - -def _decode_base64_to_bytes(data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - return base64.b64decode(data) - - -def _sign_string(key, string_to_sign, key_is_base64=True): - if key_is_base64: - key = _decode_base64_to_bytes(key) - else: - if isinstance(key, six.text_type): - key = key.encode('utf-8') - if isinstance(string_to_sign, six.text_type): - string_to_sign = string_to_sign.encode('utf-8') - signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256) - digest = signed_hmac_sha256.digest() - encoded_digest = encode_base64(digest) - return encoded_digest - - -def serialize_iso(attr): - """Serialize Datetime object into ISO-8601 formatted string. - - :param Datetime attr: Object to be serialized. - :rtype: str - :raises: ValueError if format invalid. - """ - if not attr: - return None - if isinstance(attr, str): - attr = isodate.parse_datetime(attr) - try: - utc = attr.utctimetuple() - if utc.tm_year > 9999 or utc.tm_year < 1: - raise OverflowError("Hit max or min date") - - date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( - utc.tm_year, utc.tm_mon, utc.tm_mday, - utc.tm_hour, utc.tm_min, utc.tm_sec) - return date + 'Z' - except (ValueError, OverflowError) as err: - msg = "Unable to serialize datetime object." - raise_with_traceback(ValueError, msg, err) - except AttributeError as err: - msg = "ISO-8601 object must be valid Datetime object." - raise_with_traceback(TypeError, msg, err) - - -def get_length(data): - length = None - # Check if object implements the __len__ method, covers most input cases such as bytearray. - try: - length = len(data) - except: # pylint: disable=bare-except - pass - - if not length: - # Check if the stream is a file-like stream object. - # If so, calculate the size using the file descriptor. - try: - fileno = data.fileno() - except (AttributeError, UnsupportedOperation): - pass - else: - return fstat(fileno).st_size - - # If the stream is seekable and tell() is implemented, calculate the stream size. - try: - current_position = data.tell() - data.seek(0, SEEK_END) - length = data.tell() - current_position - data.seek(current_position, SEEK_SET) - except (AttributeError, UnsupportedOperation): - pass - - return length - - -def read_length(data): - try: - if hasattr(data, 'read'): - read_data = b'' - for chunk in iter(lambda: data.read(4096), b""): - read_data += chunk - return len(read_data), read_data - if hasattr(data, '__iter__'): - read_data = b'' - for chunk in data: - read_data += chunk - return len(read_data), read_data - except: # pylint: disable=bare-except - pass - raise ValueError("Unable to calculate content length, please specify.") - - -def parse_length_from_content_range(content_range): - ''' - Parses the blob length from the content range header: bytes 1-3/65537 - ''' - if content_range is None: - return None - - # First, split in space and take the second half: '1-3/65537' - # Next, split on slash and take the second half: '65537' - # Finally, convert to an int: 65537 - return int(content_range.split(' ', 1)[1].split('/', 1)[1]) - - -def validate_and_format_range_headers( - start_range, end_range, start_range_required=True, - end_range_required=True, check_content_md5=False, align_to_page=False): - # If end range is provided, start range must be provided - if (start_range_required or end_range is not None) and start_range is None: - raise ValueError("start_range value cannot be None.") - if end_range_required and end_range is None: - raise ValueError("end_range value cannot be None.") - - # Page ranges must be 512 aligned - if align_to_page: - if start_range is not None and start_range % 512 != 0: - raise ValueError("Invalid page blob start_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(start_range)) - if end_range is not None and end_range % 512 != 511: - raise ValueError("Invalid page blob end_range: {0}. " - "The size must be aligned to a 512-byte boundary.".format(end_range)) - - # Format based on whether end_range is present - range_header = None - if end_range is not None: - range_header = 'bytes={0}-{1}'.format(start_range, end_range) - elif start_range is not None: - range_header = "bytes={0}-".format(start_range) - - # Content MD5 can only be provided for a complete range less than 4MB in size - range_validation = None - if check_content_md5: - if start_range is None or end_range is None: - raise ValueError("Both start and end range requied for MD5 content validation.") - if end_range - start_range > 4 * 1024 * 1024: - raise ValueError("Getting content MD5 for a range greater than 4MB is not supported.") - range_validation = 'true' - - return range_header, range_validation - - -def normalize_headers(headers): - normalized = {} - for key, value in headers.items(): - if key.startswith('x-ms-'): - key = key[5:] - normalized[key.lower().replace('-', '_')] = value - return normalized - - -def return_response_headers(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers) - - -def return_headers_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return normalize_headers(response_headers), deserialized - - -def return_context_and_deserialized(response, deserialized, response_headers): # pylint: disable=unused-argument - return response.location_mode, deserialized - - -def create_configuration(**kwargs): - # type: (**Any) -> Configuration - if 'connection_timeout' not in kwargs: - kwargs['connection_timeout'] = DEFAULT_SOCKET_TIMEOUT - config = Configuration(**kwargs) - config.headers_policy = StorageHeadersPolicy(**kwargs) - config.user_agent_policy = StorageUserAgentPolicy(**kwargs) - config.retry_policy = kwargs.get('retry_policy') or ExponentialRetry(**kwargs) - config.redirect_policy = RedirectPolicy(**kwargs) - config.logging_policy = StorageLoggingPolicy(**kwargs) - config.proxy_policy = ProxyPolicy(**kwargs) - config.blob_settings = StorageBlobSettings(**kwargs) - return config - - -def create_pipeline(credential, **kwargs): - # type: (Any, **Any) -> Tuple[Configuration, Pipeline] - credential_policy = None - if hasattr(credential, 'get_token'): - credential_policy = BearerTokenCredentialPolicy(credential, STORAGE_OAUTH_SCOPE) - elif isinstance(credential, SharedKeyCredentialPolicy): - credential_policy = credential - elif credential is not None: - raise TypeError("Unsupported credential: {}".format(credential)) - - config = kwargs.get('_configuration') or create_configuration(**kwargs) - if kwargs.get('_pipeline'): - return config, kwargs['_pipeline'] - transport = kwargs.get('transport') # type: HttpTransport - if not transport: - transport = RequestsTransport(config) - policies = [ - QueueMessagePolicy(), - config.headers_policy, - config.user_agent_policy, - StorageContentValidation(), - StorageRequestHook(**kwargs), - credential_policy, - ContentDecodePolicy(), - config.redirect_policy, - StorageHosts(**kwargs), - config.retry_policy, - config.logging_policy, - StorageResponseHook(**kwargs), - ] - return config, Pipeline(transport, policies=policies) - - -def parse_query(query_str): - sas_values = _QueryStringConstants.to_list() - parsed_query = {k: v[0] for k, v in parse_qs(query_str).items()} - sas_params = ["{}={}".format(k, v) for k, v in parsed_query.items() if k in sas_values] - sas_token = None - if sas_params: - sas_token = '&'.join(sas_params) - - return parsed_query.get('snapshot'), sas_token - - -def is_credential_sastoken(credential): - if not credential or not isinstance(credential, six.string_types): - return False - - sas_values = _QueryStringConstants.to_list() - parsed_query = parse_qs(credential.lstrip('?')) - if parsed_query and all([k in sas_values for k in parsed_query.keys()]): - return True - return False - - -def add_metadata_headers(metadata): - headers = {} - if metadata: - for key, value in metadata.items(): - headers['x-ms-meta-{}'.format(key)] = value - return headers - - -def process_storage_error(storage_error): - raise_error = HttpResponseError - error_code = storage_error.response.headers.get('x-ms-error-code') - error_message = storage_error.message - additional_data = {} - try: - error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response) - if error_body: - for info in error_body.iter(): - if info.tag.lower() == 'code': - error_code = info.text - elif info.tag.lower() == 'message': - error_message = info.text - else: - additional_data[info.tag] = info.text - except DecodeError: - pass - - try: - if error_code: - error_code = StorageErrorCode(error_code) - if error_code in [StorageErrorCode.condition_not_met, - StorageErrorCode.blob_overwritten]: - raise_error = ResourceModifiedError - if error_code in [StorageErrorCode.invalid_authentication_info, - StorageErrorCode.authentication_failed]: - raise_error = ClientAuthenticationError - if error_code in [StorageErrorCode.resource_not_found, - StorageErrorCode.blob_not_found, - StorageErrorCode.queue_not_found, - StorageErrorCode.container_not_found]: - raise_error = ResourceNotFoundError - if error_code in [StorageErrorCode.account_already_exists, - StorageErrorCode.account_being_created, - StorageErrorCode.resource_already_exists, - StorageErrorCode.resource_type_mismatch, - StorageErrorCode.blob_already_exists, - StorageErrorCode.queue_already_exists, - StorageErrorCode.container_already_exists, - StorageErrorCode.container_being_deleted, - StorageErrorCode.queue_being_deleted]: - raise_error = ResourceExistsError - except ValueError: - # Got an unknown error code - pass - - try: - error_message += "\nErrorCode:{}".format(error_code.value) - except AttributeError: - error_message += "\nErrorCode:{}".format(error_code) - for name, info in additional_data.items(): - error_message += "\n{}:{}".format(name, info) - - error = raise_error(message=error_message, response=storage_error.response) - error.error_code = error_code - error.additional_info = additional_data - raise error diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/models.py b/sdk/storage/azure-storage-queue/azure/storage/queue/models.py index 224508d959fc..d9349c4fdb63 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/models.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/models.py @@ -8,9 +8,7 @@ from typing import List # pylint: disable=unused-import from azure.core.paging import Paged -from ._shared.utils import ( - return_context_and_deserialized, - process_storage_error) +from ._shared.response_handlers import return_context_and_deserialized, process_storage_error from ._shared.models import DictMixin from ._generated.models import StorageErrorException from ._generated.models import AccessPolicy as GenAccessPolicy diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/queue_client.py index a8d58b638f73..22aa8aad7b17 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 @@ -17,16 +17,12 @@ import six from ._shared.shared_access_signature import QueueSharedAccessSignature -from ._shared.utils import ( - StorageAccountHostsMixin, - add_metadata_headers, +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, - parse_query, - serialize_iso, - parse_connection_str -) + return_headers_and_deserialized) from ._queue_utils import ( TextXMLEncodePolicy, TextXMLDecodePolicy, diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/queue_service_client.py b/sdk/storage/azure-storage-queue/azure/storage/queue/queue_service_client.py index 41be25c1d6bf..105074a61b11 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/queue_service_client.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/queue_service_client.py @@ -15,11 +15,8 @@ from ._shared.shared_access_signature import SharedAccessSignature from ._shared.models import LocationMode, Services -from ._shared.utils import ( - StorageAccountHostsMixin, - parse_query, - parse_connection_str, - process_storage_error) +from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query +from ._shared.response_handlers import process_storage_error from ._generated import AzureQueueStorage from ._generated.models import StorageServiceProperties, StorageErrorException diff --git a/sdk/storage/azure-storage-queue/dev_requirements.txt b/sdk/storage/azure-storage-queue/dev_requirements.txt index 78ea6cb44052..70bc75a7b9d7 100644 --- a/sdk/storage/azure-storage-queue/dev_requirements.txt +++ b/sdk/storage/azure-storage-queue/dev_requirements.txt @@ -1,5 +1,5 @@ -e ../../../tools/azure-sdk-tools -e ../../identity/azure-identity -pylint==2.1.1; python_version >= '3.4' +pylint==2.3.1; python_version >= '3.4' pylint==1.8.4; python_version < '3.4' aiohttp>=3.0; python_version >= '3.5' diff --git a/sdk/storage/azure-storage-queue/tests/test_queue_encryption.py b/sdk/storage/azure-storage-queue/tests/test_queue_encryption.py index 37c15163ee40..4196991ebd55 100644 --- a/sdk/storage/azure-storage-queue/tests/test_queue_encryption.py +++ b/sdk/storage/azure-storage-queue/tests/test_queue_encryption.py @@ -20,7 +20,7 @@ from azure.core.exceptions import HttpResponseError, ResourceExistsError -from azure.storage.queue._shared.utils import _decode_base64_to_bytes +from azure.storage.queue._shared import decode_base64_to_bytes from azure.storage.queue._shared.encryption import ( _ERROR_OBJECT_INVALID, _WrappedContentKey, @@ -407,7 +407,7 @@ def test_validate_encryption(self): cipher = Cipher(algorithm, mode, backend) # decode and decrypt data - decrypted_data = _decode_base64_to_bytes(message) + decrypted_data = decode_base64_to_bytes(message) decryptor = cipher.decryptor() decrypted_data = (decryptor.update(decrypted_data) + decryptor.finalize()) diff --git a/tools/Dockerfile b/tools/Dockerfile new file mode 100644 index 000000000000..27a3832ee341 --- /dev/null +++ b/tools/Dockerfile @@ -0,0 +1,34 @@ +FROM ubuntu +MAINTAINER zikalino + +RUN apt-get update +RUN apt-get install -y git curl gnupg vim python3 python3-pip git software-properties-common apt-transport-https wget python3-venv nodejs npm libunwind-dev + +# install dotnet +RUN add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" +RUN apt-get update +RUN apt-get install -y libicu55 + +RUN wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb +RUN dpkg -i packages-microsoft-prod.deb +RUN apt-get update +RUN apt-get install -y dotnet-sdk-2.2 + +# install autorest +RUN npm install -g autorest +RUN npm install -g n +RUN n 10.15.0 + +RUN pip3 install wheel + +# clone repos +RUN git clone https://github.com/Azure/azure-rest-api-specs.git +RUN git clone https://github.com/Azure/azure-sdk-for-python.git + +# setup virtual env +RUN cd /azure-sdk-for-python; python3 -m venv env3.7 +ENV VIRTUAL_ENV /azure-sdk-for-python;/env3.7 +ENV PATH /azure-sdk-for-python/env3.7/bin:$PATH + +# run setup +RUN cd /azure-sdk-for-python; python ./scripts/dev_setup.py