From 2635f6567abddfcb30e8f8c6e6d63316105fe974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= <39780829+mccoyp@users.noreply.github.com> Date: Fri, 16 Oct 2020 16:41:55 -0700 Subject: [PATCH] Add parse_key_vault_certificate_id method and tests (#14518) --- .../azure-keyvault-certificates/CHANGELOG.md | 3 + .../azure/keyvault/certificates/__init__.py | 6 +- .../azure/keyvault/certificates/_models.py | 16 +- .../azure/keyvault/certificates/_parse_id.py | 29 ++ .../keyvault/certificates/_shared/__init__.py | 43 ++- ...est_parse_certificate_id_with_version.yaml | 331 ++++++++++++++++++ .../tests/test_certificates_client.py | 20 +- .../tests/test_certificates_client_async.py | 21 +- .../tests/test_parse_id.py | 62 ++++ 9 files changed, 490 insertions(+), 41 deletions(-) create mode 100644 sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_parse_id.py create mode 100644 sdk/keyvault/azure-keyvault-certificates/tests/recordings/test_parse_id.test_parse_certificate_id_with_version.yaml create mode 100644 sdk/keyvault/azure-keyvault-certificates/tests/test_parse_id.py diff --git a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md index 03a64a577896..2da93d91cc84 100644 --- a/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md +++ b/sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md @@ -1,6 +1,9 @@ # Release History ## 4.2.2 (Unreleased) +### Added +- Added method `parse_key_vault_certificate_id` that parses out a full ID returned by Key Vault, so users can easily +access the certificate's `name`, `vault_url`, and `version`. ## 4.2.1 (2020-09-08) diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/__init__.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/__init__.py index 31d88019da14..81aec5e95261 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/__init__.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/__init__.py @@ -24,7 +24,9 @@ LifetimeAction, KeyVaultCertificate ) +from ._parse_id import parse_key_vault_certificate_id from ._shared.client_base import ApiVersion +from ._shared import KeyVaultResourceId __all__ = [ "ApiVersion", @@ -47,7 +49,9 @@ "CertificateContentType", "WellKnownIssuerNames", "CertificateIssuer", - "IssuerProperties" + "IssuerProperties", + "parse_key_vault_certificate_id", + "KeyVaultResourceId" ] from ._version import VERSION diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_models.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_models.py index a73c8e359ef4..c4d52201e92d 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_models.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_models.py @@ -4,7 +4,7 @@ # ------------------------------------ # pylint: disable=too-many-lines,too-many-public-methods -from ._shared import parse_vault_id +from ._shared import parse_key_vault_id from ._generated.v7_1 import models from ._enums import( CertificatePolicyAction, @@ -147,7 +147,7 @@ def __init__(self, **kwargs): # type: (**Any) -> None self._attributes = kwargs.pop("attributes", None) self._id = kwargs.pop("cert_id", None) - self._vault_id = parse_vault_id(self._id) + self._vault_id = parse_key_vault_id(self._id) self._x509_thumbprint = kwargs.pop("x509_thumbprint", None) self._tags = kwargs.pop("tags", None) @@ -430,7 +430,7 @@ def __init__( ): # type: (...) -> None self._id = cert_operation_id - self._vault_id = parse_vault_id(cert_operation_id) + self._vault_id = parse_key_vault_id(cert_operation_id) self._issuer_name = issuer_name self._certificate_type = certificate_type self._certificate_transparency = certificate_transparency @@ -1058,7 +1058,7 @@ class IssuerProperties(object): def __init__(self, provider=None, **kwargs): # type: (Optional[str], **Any) -> None self._id = kwargs.pop("issuer_id", None) - self._vault_id = parse_vault_id(self._id) + self._vault_id = parse_key_vault_id(self._id) self._provider = provider def __repr__(self): @@ -1120,7 +1120,7 @@ def __init__( self._organization_id = organization_id self._admin_contacts = admin_contacts self._id = kwargs.pop("issuer_id", None) - self._vault_id = parse_vault_id(self._id) + self._vault_id = parse_key_vault_id(self._id) def __repr__(self): # type () -> str @@ -1157,9 +1157,9 @@ def id(self): @property def name(self): # type: () -> str - # Issuer name is listed under version under vault_id - # This is because the id we pass to parse_vault_id has an extra segment, so where most cases the version of - # The general pattern is certificates/name/version, but here we have certificates/issuers/name/version + # Issuer name is listed under version under vault_id. + # This is because the id we pass to parse_key_vault_id has an extra segment, so where most cases the version of + # the general pattern is certificates/name/version, but here we have certificates/issuers/name/version. # Issuers are not versioned. """:rtype: str""" return self._vault_id.version diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_parse_id.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_parse_id.py new file mode 100644 index 000000000000..e360b70b5da8 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_parse_id.py @@ -0,0 +1,29 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +from ._shared import parse_key_vault_id, KeyVaultResourceId + + +def parse_key_vault_certificate_id(source_id): + # type: (str) -> KeyVaultResourceId + """Parses a certificate's full ID into a class with parsed contents as attributes. + + :param str source_id: the full original identifier of a certificate + :returns: Returns a parsed certificate ID as a :class:`KeyVaultResourceId` + :rtype: ~azure.keyvault.certificates.KeyVaultResourceId + :raises: ValueError + Example: + .. literalinclude:: ../tests/test_parse_id.py + :start-after: [START parse_key_vault_certificate_id] + :end-before: [END parse_key_vault_certificate_id] + :language: python + :caption: Parse a certificate's ID + :dedent: 8 + """ + parsed_id = parse_key_vault_id(source_id) + + return KeyVaultResourceId( + name=parsed_id.name, source_id=parsed_id.source_id, vault_url=parsed_id.vault_url, version=parsed_id.version + ) diff --git a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py index e13f15a61c71..3c99166efa6b 100644 --- a/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py +++ b/sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_shared/__init__.py @@ -2,19 +2,22 @@ # 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 typing import TYPE_CHECKING from .challenge_auth_policy import ChallengeAuthPolicy, ChallengeAuthPolicyBase from .client_base import KeyVaultClientBase from .http_challenge import HttpChallenge from . import http_challenge_cache as HttpChallengeCache +if TYPE_CHECKING: + # pylint: disable=unused-import + from typing import Optional + __all__ = [ "ChallengeAuthPolicy", @@ -24,25 +27,45 @@ "KeyVaultClientBase", ] -_VaultId = namedtuple("VaultId", ["vault_url", "collection", "name", "version"]) +class KeyVaultResourceId(): + """Represents a Key Vault identifier and its parsed contents. + + :param str source_id: The complete identifier received from Key Vault + :param str vault_url: The vault URL + :param str name: The name extracted from the ID + :param str version: The version extracted from the ID + """ + + def __init__( + self, + source_id, # type: str + vault_url, # type: str + name, # type: str + version=None # type: Optional[str] + ): + self.source_id = source_id + self.vault_url = vault_url + self.name = name + self.version = version -def parse_vault_id(url): +def parse_key_vault_id(source_id): + # type: (str) -> KeyVaultResourceId try: - parsed_uri = parse.urlparse(url) + parsed_uri = parse.urlparse(source_id) except Exception: # pylint: disable=broad-except - raise ValueError("'{}' is not not a valid url".format(url)) + raise ValueError("'{}' is not not a valid ID".format(source_id)) if not (parsed_uri.scheme and parsed_uri.hostname): - raise ValueError("'{}' is not not a valid url".format(url)) + raise ValueError("'{}' is not not a valid ID".format(source_id)) 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)) + raise ValueError("'{}' is not not a valid vault ID".format(source_id)) - return _VaultId( + return KeyVaultResourceId( + source_id=source_id, vault_url="{}://{}".format(parsed_uri.scheme, parsed_uri.hostname), - collection=path[0], name=path[1], version=path[2] if len(path) == 3 else None, ) diff --git a/sdk/keyvault/azure-keyvault-certificates/tests/recordings/test_parse_id.test_parse_certificate_id_with_version.yaml b/sdk/keyvault/azure-keyvault-certificates/tests/recordings/test_parse_id.test_parse_certificate_id_with_version.yaml new file mode 100644 index 000000000000..64ebd555a1d5 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-certificates/tests/recordings/test_parse_id.test_parse_certificate_id_with_version.yaml @@ -0,0 +1,331 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/create?api-version=7.1 + response: + body: + string: '{"error":{"code":"Unauthorized","message":"Request is missing a Bearer + or PoP token."}}' + headers: + cache-control: + - no-cache + content-length: + - '87' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:14 GMT + expires: + - '-1' + pragma: + - no-cache + 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-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 401 + message: Unauthorized +- request: + body: '{"policy": {"issuer": {"name": "Self"}, "x509_props": {"subject": "CN=DefaultPolicy", + "sans": {}}}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '99' + Content-Type: + - application/json + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/create?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending","issuer":{"name":"Self"},"csr":"MIICqDCCAZACAQAwGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaBLMEkGCSqGSIb3DQEJDjE8MDowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBW/gnlVgImxYlW8cVmrBc19QRmELKOhvV9+vNEOaZx9PnSWnntyEcOkvE19mtKLucBQZqLjqUtBYfh5vxdQgndEnfbhLqUC+OrgpLNRY/SLOuzCdz+SJLtvB1JsRF3f0HSgXvEy460p54v3W95oUZJZx2YCpsIZS3Vi6oIq3oRL3Zu2hj9MwTn6Ghguo+DHmE4KS3HBc8s23CLfr81UNXlpstHrhYmkkKjP0uJ0IVKZ1HBx9Y2eTbERV+31K9dMYCStahNKGID04hpL94e1hg2/pIrlpkJD3tq1I/wD0HzQgj7DWHm7J7GSdxbHO7gMntxkhgns+yPZN4e3YCveTRv","cancellation_requested":false,"status":"inProgress","status_details":"Pending + certificate created. Certificate request is in progress. This may take some + time based on the issuer provider. Please check again later.","request_id":"1fb9b2cef9804791be79eed316fdc4c6"}' + headers: + cache-control: + - no-cache + content-length: + - '1302' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:17 GMT + expires: + - '-1' + location: + - https://vaultname.vault.azure.net/certificates/cert3382155d/pending?api-version=7.1&request_id=1fb9b2cef9804791be79eed316fdc4c6 + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 202 + message: Accepted +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/pending?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending","issuer":{"name":"Self"},"csr":"MIICqDCCAZACAQAwGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaBLMEkGCSqGSIb3DQEJDjE8MDowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBW/gnlVgImxYlW8cVmrBc19QRmELKOhvV9+vNEOaZx9PnSWnntyEcOkvE19mtKLucBQZqLjqUtBYfh5vxdQgndEnfbhLqUC+OrgpLNRY/SLOuzCdz+SJLtvB1JsRF3f0HSgXvEy460p54v3W95oUZJZx2YCpsIZS3Vi6oIq3oRL3Zu2hj9MwTn6Ghguo+DHmE4KS3HBc8s23CLfr81UNXlpstHrhYmkkKjP0uJ0IVKZ1HBx9Y2eTbERV+31K9dMYCStahNKGID04hpL94e1hg2/pIrlpkJD3tq1I/wD0HzQgj7DWHm7J7GSdxbHO7gMntxkhgns+yPZN4e3YCveTRv","cancellation_requested":false,"status":"inProgress","status_details":"Pending + certificate created. Certificate request is in progress. This may take some + time based on the issuer provider. Please check again later.","request_id":"1fb9b2cef9804791be79eed316fdc4c6"}' + headers: + cache-control: + - no-cache + content-length: + - '1302' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:17 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/pending?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending","issuer":{"name":"Self"},"csr":"MIICqDCCAZACAQAwGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaBLMEkGCSqGSIb3DQEJDjE8MDowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBW/gnlVgImxYlW8cVmrBc19QRmELKOhvV9+vNEOaZx9PnSWnntyEcOkvE19mtKLucBQZqLjqUtBYfh5vxdQgndEnfbhLqUC+OrgpLNRY/SLOuzCdz+SJLtvB1JsRF3f0HSgXvEy460p54v3W95oUZJZx2YCpsIZS3Vi6oIq3oRL3Zu2hj9MwTn6Ghguo+DHmE4KS3HBc8s23CLfr81UNXlpstHrhYmkkKjP0uJ0IVKZ1HBx9Y2eTbERV+31K9dMYCStahNKGID04hpL94e1hg2/pIrlpkJD3tq1I/wD0HzQgj7DWHm7J7GSdxbHO7gMntxkhgns+yPZN4e3YCveTRv","cancellation_requested":false,"status":"inProgress","status_details":"Pending + certificate created. Certificate request is in progress. This may take some + time based on the issuer provider. Please check again later.","request_id":"1fb9b2cef9804791be79eed316fdc4c6"}' + headers: + cache-control: + - no-cache + content-length: + - '1302' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/pending?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending","issuer":{"name":"Self"},"csr":"MIICqDCCAZACAQAwGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaBLMEkGCSqGSIb3DQEJDjE8MDowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBW/gnlVgImxYlW8cVmrBc19QRmELKOhvV9+vNEOaZx9PnSWnntyEcOkvE19mtKLucBQZqLjqUtBYfh5vxdQgndEnfbhLqUC+OrgpLNRY/SLOuzCdz+SJLtvB1JsRF3f0HSgXvEy460p54v3W95oUZJZx2YCpsIZS3Vi6oIq3oRL3Zu2hj9MwTn6Ghguo+DHmE4KS3HBc8s23CLfr81UNXlpstHrhYmkkKjP0uJ0IVKZ1HBx9Y2eTbERV+31K9dMYCStahNKGID04hpL94e1hg2/pIrlpkJD3tq1I/wD0HzQgj7DWHm7J7GSdxbHO7gMntxkhgns+yPZN4e3YCveTRv","cancellation_requested":false,"status":"completed","target":"https://vaultname.vault.azure.net/certificates/cert3382155d","request_id":"1fb9b2cef9804791be79eed316fdc4c6"}' + headers: + cache-control: + - no-cache + content-length: + - '1223' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:27 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/f91e6e5e73504e7f87134378c9112204","kid":"https://vaultname.vault.azure.net/keys/cert3382155d/f91e6e5e73504e7f87134378c9112204","sid":"https://vaultname.vault.azure.net/secrets/cert3382155d/f91e6e5e73504e7f87134378c9112204","x5t":"vi5l5__fOMTzb1mgOAni3tma1F4","cer":"MIIDNjCCAh6gAwIBAgIQJIR8MCGBQaaL0U2EzAqIIjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDEw1EZWZhdWx0UG9saWN5MB4XDTIwMTAxNTAwNDIyMloXDTIxMTAxNTAwNTIyMlowGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaN8MHowDgYDVR0PAQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFNiCZ9cnE9Tjvip/iwbciKV+KlMyMB0GA1UdDgQWBBTYgmfXJxPU474qf4sG3IilfipTMjANBgkqhkiG9w0BAQsFAAOCAQEADPw45/uVTanMUpxjOlm+3Gt+fOfmVV1cRoKjNBA/vA9QZlNkvQFxY6sme+HzNkqzIc/z/N2gFfSgHMiVzwkIXsFgaaH4bSgZ7r6G1aCx3J1yCoLSZCWbEJvQuAQIZnB/BvULTK9y9zOeC6JQO36OCQrR1KwyzjxNLfMFejVxm/OR8b+p51ZN7xJaL8oG1Bw3QRwV8+AHHqBGzO/Pawk+Dz8VDfYQ79/1OdJAf0uZCyXUE2fF7F/Vs8VD0DoEd6wLbecUhmu6HcmWhVHjtkJwCZe7JHrRFymRTDJv6NgJZ8ytcBcOhjy82oEScqc+1lcOM9r1dppMXhPRSp7dfU6zpQ==","attributes":{"enabled":true,"nbf":1602722542,"exp":1634259142,"created":1602723143,"updated":1602723143,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90},"policy":{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/policy","key_props":{"exportable":true,"kty":"RSA","key_size":2048,"reuse_key":false},"secret_props":{"contentType":"application/x-pkcs12"},"x509_props":{"subject":"CN=DefaultPolicy","sans":{},"ekus":["1.3.6.1.5.5.7.3.1","1.3.6.1.5.5.7.3.2"],"key_usage":["digitalSignature","keyEncipherment"],"validity_months":12,"basic_constraints":{"ca":false}},"lifetime_actions":[{"trigger":{"lifetime_percentage":80},"action":{"action_type":"AutoRenew"}}],"issuer":{"name":"Self"},"attributes":{"enabled":true,"created":1602723136,"updated":1602723136}},"pending":{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending"}}' + headers: + cache-control: + - no-cache + content-length: + - '2387' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:27 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-keyvault-certificates/4.2.2 Python/3.5.3 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://vaultname.vault.azure.net/certificates/cert3382155d/?api-version=7.1 + response: + body: + string: '{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/f91e6e5e73504e7f87134378c9112204","kid":"https://vaultname.vault.azure.net/keys/cert3382155d/f91e6e5e73504e7f87134378c9112204","sid":"https://vaultname.vault.azure.net/secrets/cert3382155d/f91e6e5e73504e7f87134378c9112204","x5t":"vi5l5__fOMTzb1mgOAni3tma1F4","cer":"MIIDNjCCAh6gAwIBAgIQJIR8MCGBQaaL0U2EzAqIIjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDEw1EZWZhdWx0UG9saWN5MB4XDTIwMTAxNTAwNDIyMloXDTIxMTAxNTAwNTIyMlowGDEWMBQGA1UEAxMNRGVmYXVsdFBvbGljeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMauvCl4utNKFCErlckBT5SG9h+BDm9mVVhruEgZZaQ5EcOe2bfOLplfJfuOO4qLapwT81INcWuUnxyqH89UBa05rZBr7a+9Zvrai2KqtoWXfSHHecJPma86NDS+6jCCpwynCgkIXYd2FrxRXcrB0kSXN39XTcBw5fNnquCMbpf9+Wja4MTBZbdIF1wcB47NL2/usq/fzGKOHet/8clsYGtP67qAd5lspjEjkalPi9TVzkV3b48TFg+0Zr7BmM9gGMN4jF57N02oqpnRIfxC7HLs5WxJOeAL5SXuW6LBH+xhQcy0utjvxqTKbwHFRmyG7wkqIeaVbmt3j82kkwjUz0CAwEAAaN8MHowDgYDVR0PAQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFNiCZ9cnE9Tjvip/iwbciKV+KlMyMB0GA1UdDgQWBBTYgmfXJxPU474qf4sG3IilfipTMjANBgkqhkiG9w0BAQsFAAOCAQEADPw45/uVTanMUpxjOlm+3Gt+fOfmVV1cRoKjNBA/vA9QZlNkvQFxY6sme+HzNkqzIc/z/N2gFfSgHMiVzwkIXsFgaaH4bSgZ7r6G1aCx3J1yCoLSZCWbEJvQuAQIZnB/BvULTK9y9zOeC6JQO36OCQrR1KwyzjxNLfMFejVxm/OR8b+p51ZN7xJaL8oG1Bw3QRwV8+AHHqBGzO/Pawk+Dz8VDfYQ79/1OdJAf0uZCyXUE2fF7F/Vs8VD0DoEd6wLbecUhmu6HcmWhVHjtkJwCZe7JHrRFymRTDJv6NgJZ8ytcBcOhjy82oEScqc+1lcOM9r1dppMXhPRSp7dfU6zpQ==","attributes":{"enabled":true,"nbf":1602722542,"exp":1634259142,"created":1602723143,"updated":1602723143,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90},"policy":{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/policy","key_props":{"exportable":true,"kty":"RSA","key_size":2048,"reuse_key":false},"secret_props":{"contentType":"application/x-pkcs12"},"x509_props":{"subject":"CN=DefaultPolicy","sans":{},"ekus":["1.3.6.1.5.5.7.3.1","1.3.6.1.5.5.7.3.2"],"key_usage":["digitalSignature","keyEncipherment"],"validity_months":12,"basic_constraints":{"ca":false}},"lifetime_actions":[{"trigger":{"lifetime_percentage":80},"action":{"action_type":"AutoRenew"}}],"issuer":{"name":"Self"},"attributes":{"enabled":true,"created":1602723136,"updated":1602723136}},"pending":{"id":"https://vaultname.vault.azure.net/certificates/cert3382155d/pending"}}' + headers: + cache-control: + - no-cache + content-length: + - '2387' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 15 Oct 2020 00:52:27 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000;includeSubDomains + x-content-type-options: + - nosniff + x-ms-keyvault-network-info: + - conn_type=Ipv4;addr=162.211.216.102;act_addr_fam=InterNetwork; + x-ms-keyvault-region: + - westus + x-ms-keyvault-service-version: + - 1.2.41.0 + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client.py b/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client.py index ba7ccd1cb36f..01f9e2eda737 100644 --- a/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client.py +++ b/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client.py @@ -24,9 +24,8 @@ LifetimeAction, CertificateIssuer, IssuerProperties, - ApiVersion, + parse_key_vault_certificate_id ) -from azure.keyvault.certificates._shared import parse_vault_id from devtools_testutils import ResourceGroupPreparer, KeyVaultPreparer from _shared.preparer import KeyVaultClientPreparer as _KeyVaultClientPreparer @@ -85,7 +84,7 @@ def _validate_certificate_operation(self, pending_cert_operation, vault, cert_na self.assertIsNotNone(pending_cert_operation) self.assertIsNotNone(pending_cert_operation.csr) self.assertEqual(original_cert_policy.issuer_name, pending_cert_operation.issuer_name) - pending_id = parse_vault_id(pending_cert_operation.id) + pending_id = parse_key_vault_certificate_id(pending_cert_operation.id) self.assertEqual(pending_id.vault_url.strip("/"), vault.strip("/")) self.assertEqual(pending_id.name, cert_name) @@ -260,9 +259,10 @@ def test_list(self, client, **kwargs): error_count = 0 try: cert_bundle = self._import_common_certificate(client=client, cert_name=cert_name) - parsed_id = parse_vault_id(url=cert_bundle.id) - cid = parsed_id.vault_url + "/" + parsed_id.collection + "/" + parsed_id.name - expected[cid.strip("/")] = cert_bundle + # Going to remove the ID from the last '/' onwards. This is because list_properties_of_certificates + # doesn't return the version in the ID + cid = "/".join(cert_bundle.id.split("/")[:-1]) + expected[cid] = cert_bundle except Exception as ex: if hasattr(ex, "message") and "Throttled" in ex.message: error_count += 1 @@ -289,9 +289,7 @@ def test_list_certificate_versions(self, client, **kwargs): error_count = 0 try: cert_bundle = self._import_common_certificate(client=client, cert_name=cert_name) - parsed_id = parse_vault_id(url=cert_bundle.id) - cid = parsed_id.vault_url + "/" + parsed_id.collection + "/" + parsed_id.name + "/" + parsed_id.version - expected[cid.strip("/")] = cert_bundle + expected[cert_bundle.id] = cert_bundle except Exception as ex: if hasattr(ex, "message") and "Throttled" in ex.message: error_count += 1 @@ -357,7 +355,7 @@ def test_recover_and_purge(self, client, **kwargs): client.begin_delete_certificate(certificate_name=cert_name).wait() # validate all our deleted certificates are returned by list_deleted_certificates - deleted = [parse_vault_id(url=c.id).name for c in client.list_deleted_certificates()] + deleted = [parse_key_vault_certificate_id(source_id=c.id).name for c in client.list_deleted_certificates()] self.assertTrue(all(c in deleted for c in certs.keys())) # recover select certificates @@ -372,7 +370,7 @@ def test_recover_and_purge(self, client, **kwargs): time.sleep(50) # validate none of our deleted certificates are returned by list_deleted_certificates - deleted = [parse_vault_id(url=c.id).name for c in client.list_deleted_certificates()] + deleted = [parse_key_vault_certificate_id(source_id=c.id).name for c in client.list_deleted_certificates()] self.assertTrue(not any(c in deleted for c in certs.keys())) # validate the recovered certificates diff --git a/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client_async.py b/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client_async.py index be6bf45f03ab..aedaad9e84f6 100644 --- a/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client_async.py +++ b/sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client_async.py @@ -10,6 +10,7 @@ from azure_devtools.scenario_tests import RecordingProcessor from azure.keyvault.certificates import ( AdministratorContact, + ApiVersion, CertificateContact, CertificatePolicyAction, CertificatePolicy, @@ -20,10 +21,9 @@ LifetimeAction, CertificateIssuer, IssuerProperties, - ApiVersion, + parse_key_vault_certificate_id ) from azure.keyvault.certificates.aio import CertificateClient -from azure.keyvault.certificates._shared import parse_vault_id from devtools_testutils import ResourceGroupPreparer, KeyVaultPreparer import pytest @@ -78,7 +78,7 @@ def _validate_certificate_operation(self, pending_cert_operation, vault, cert_na self.assertIsNotNone(pending_cert_operation) self.assertIsNotNone(pending_cert_operation.csr) self.assertEqual(original_cert_policy.issuer_name, pending_cert_operation.issuer_name) - pending_id = parse_vault_id(pending_cert_operation.id) + pending_id = parse_key_vault_certificate_id(pending_cert_operation.id) self.assertEqual(pending_id.vault_url.strip("/"), vault.strip("/")) self.assertEqual(pending_id.name, cert_name) @@ -254,9 +254,10 @@ async def test_list(self, client, **kwargs): error_count = 0 try: cert_bundle = await self._import_common_certificate(client=client, cert_name=cert_name) - parsed_id = parse_vault_id(url=cert_bundle.id) - cid = parsed_id.vault_url + "/" + parsed_id.collection + "/" + parsed_id.name - expected[cid.strip("/")] = cert_bundle + # Going to remove the ID from the last '/' onwards. This is because list_properties_of_certificates + # doesn't return the version in the ID + cid = "/".join(cert_bundle.id.split("/")[:-1]) + expected[cid] = cert_bundle except Exception as ex: if hasattr(ex, "message") and "Throttled" in ex.message: error_count += 1 @@ -283,9 +284,7 @@ async def test_list_certificate_versions(self, client, **kwargs): error_count = 0 try: cert_bundle = await self._import_common_certificate(client=client, cert_name=cert_name) - parsed_id = parse_vault_id(url=cert_bundle.id) - cid = parsed_id.vault_url + "/" + parsed_id.collection + "/" + parsed_id.name + "/" + parsed_id.version - expected[cid.strip("/")] = cert_bundle + expected[cert_bundle.id.strip("/")] = cert_bundle except Exception as ex: if hasattr(ex, "message") and "Throttled" in ex.message: error_count += 1 @@ -356,7 +355,7 @@ async def test_recover_and_purge(self, client, **kwargs): deleted_certificates = client.list_deleted_certificates() deleted = [] async for c in deleted_certificates: - deleted.append(parse_vault_id(url=c.id).name) + deleted.append(parse_key_vault_certificate_id(source_id=c.id).name) self.assertTrue(all(c in deleted for c in certs.keys())) # recover select certificates @@ -374,7 +373,7 @@ async def test_recover_and_purge(self, client, **kwargs): deleted_certificates = client.list_deleted_certificates() deleted = [] async for c in deleted_certificates: - deleted.append(parse_vault_id(url=c.id).name) + deleted.append(parse_key_vault_certificate_id(source_id=c.id).name) self.assertTrue(not any(c in deleted for c in certs.keys())) # validate the recovered certificates diff --git a/sdk/keyvault/azure-keyvault-certificates/tests/test_parse_id.py b/sdk/keyvault/azure-keyvault-certificates/tests/test_parse_id.py new file mode 100644 index 000000000000..1b35902bac19 --- /dev/null +++ b/sdk/keyvault/azure-keyvault-certificates/tests/test_parse_id.py @@ -0,0 +1,62 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------- +import pytest +import functools +from azure.keyvault.certificates import CertificateClient, CertificatePolicy, parse_key_vault_certificate_id +from devtools_testutils import ResourceGroupPreparer, KeyVaultPreparer + +from _shared.preparer import KeyVaultClientPreparer as _KeyVaultClientPreparer +from _shared.test_case import KeyVaultTestCase + +# pre-apply the client_cls positional argument so it needn't be explicitly passed below +KeyVaultClientPreparer = functools.partial(_KeyVaultClientPreparer, CertificateClient) + + +class TestParseId(KeyVaultTestCase): + @ResourceGroupPreparer(random_name_enabled=True) + @KeyVaultPreparer() + @KeyVaultClientPreparer() + def test_parse_certificate_id_with_version(self, client): + cert_name = self.get_resource_name("cert") + # create certificate + certificate = client.begin_create_certificate(cert_name, CertificatePolicy.get_default()).result() + + # [START parse_key_vault_certificate_id] + cert = client.get_certificate(cert_name) + parsed_certificate_id = parse_key_vault_certificate_id(cert.id) + + print(parsed_certificate_id.name) + print(parsed_certificate_id.vault_url) + print(parsed_certificate_id.version) + print(parsed_certificate_id.source_id) + # [END parse_key_vault_certificate_id] + self.assertEqual(parsed_certificate_id.name, cert_name) + self.assertEqual(parsed_certificate_id.vault_url, client.vault_url) + self.assertEqual(parsed_certificate_id.version, cert.properties.version) + self.assertEqual(parsed_certificate_id.source_id, cert.id) + + def test_parse_certificate_id_with_pending_version(self): + source_id = "https://keyvault-name.vault.azure.net/certificates/certificate-name/pending" + parsed_certificate_id = parse_key_vault_certificate_id(source_id) + + self.assertEqual(parsed_certificate_id.name, "certificate-name") + self.assertEqual(parsed_certificate_id.vault_url, "https://keyvault-name.vault.azure.net") + self.assertEqual(parsed_certificate_id.version, "pending") + self.assertEqual( + parsed_certificate_id.source_id, + "https://keyvault-name.vault.azure.net/certificates/certificate-name/pending", + ) + + def test_parse_deleted_certificate_id(self): + source_id = "https://keyvault-name.vault.azure.net/deletedcertificates/deleted-certificate" + parsed_certificate_id = parse_key_vault_certificate_id(source_id) + + self.assertEqual(parsed_certificate_id.name, "deleted-certificate") + self.assertEqual(parsed_certificate_id.vault_url, "https://keyvault-name.vault.azure.net") + self.assertIsNone(parsed_certificate_id.version) + self.assertEqual( + parsed_certificate_id.source_id, + "https://keyvault-name.vault.azure.net/deletedcertificates/deleted-certificate", + )