Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sdk/keyvault/azure-keyvault-secrets/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
### Fixed
- Correct typing for async paging methods

### Added
- Added method `parse_key_vault_secret_id` that parses out a full ID returned by Key Vault, so users can easily
access the secret's `name`, `vault_url`, and `version`.

## 4.2.0 (2020-08-11)
### Fixed
- Values of `x-ms-keyvault-region` and `x-ms-keyvault-service-version` headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
# Licensed under the MIT License.
# ------------------------------------
from ._models import DeletedSecret, KeyVaultSecret, SecretProperties
from ._parse_id import parse_key_vault_secret_id
from ._shared import KeyVaultResourceId
from ._shared.client_base import ApiVersion
from ._client import SecretClient

__all__ = ["ApiVersion", "SecretClient", "KeyVaultSecret", "SecretProperties", "DeletedSecret"]
__all__ = [
"ApiVersion",
"SecretClient",
"KeyVaultSecret",
"SecretProperties",
"DeletedSecret",
"parse_key_vault_secret_id",
"KeyVaultResourceId"
]

from ._version import VERSION
__version__ = VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
from ._shared import parse_vault_id
from ._shared import parse_key_vault_id

try:
from typing import TYPE_CHECKING
Expand All @@ -23,7 +23,7 @@ def __init__(self, attributes, vault_id, **kwargs):
# type: (_models.SecretAttributes, str, **Any) -> None
self._attributes = attributes
self._id = vault_id
self._vault_id = parse_vault_id(vault_id)
self._vault_id = parse_key_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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------

from ._shared import parse_key_vault_id, KeyVaultResourceId


def parse_key_vault_secret_id(source_id):
# type: (str) -> KeyVaultResourceId
"""Parses a secret's full ID into a class with parsed contents as attributes.
:param str source_id: the full original identifier of a secret
:returns: Returns a parsed secret ID as a :class:`KeyVaultResourceId`
:rtype: ~azure.keyvault.secrets.KeyVaultResourceId
:raises: ValueError
Example:
.. literalinclude:: ../tests/test_parse_id.py
:start-after: [START parse_key_vault_secret_id]
:end-before: [END parse_key_vault_secret_id]
:language: python
:caption: Parse a secret'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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -24,25 +27,44 @@
"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 url".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 url".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 url".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,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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-secrets/4.2.1 Python/3.5.3 (Windows-10-10.0.19041-SP0)
method: PUT
uri: https://vaultname.vault.azure.net/secrets/secretce671360?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:
- Fri, 06 Nov 2020 01:59:17 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.58.0
x-powered-by:
- ASP.NET
status:
code: 401
message: Unauthorized
- request:
body: '{"value": "secret_value"}'
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '25'
Content-Type:
- application/json
User-Agent:
- azsdk-python-keyvault-secrets/4.2.1 Python/3.5.3 (Windows-10-10.0.19041-SP0)
method: PUT
uri: https://vaultname.vault.azure.net/secrets/secretce671360?api-version=7.1
response:
body:
string: '{"value":"secret_value","id":"https://vaultname.vault.azure.net/secrets/secretce671360/82b4c68fbf654bff953bd26a4a5f5f1e","attributes":{"enabled":true,"created":1604627958,"updated":1604627958,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}'
headers:
cache-control:
- no-cache
content-length:
- '269'
content-type:
- application/json; charset=utf-8
date:
- Fri, 06 Nov 2020 01:59: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.58.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-secrets/4.2.1 Python/3.5.3 (Windows-10-10.0.19041-SP0)
method: GET
uri: https://vaultname.vault.azure.net/secrets/secretce671360/?api-version=7.1
response:
body:
string: '{"value":"secret_value","id":"https://vaultname.vault.azure.net/secrets/secretce671360/82b4c68fbf654bff953bd26a4a5f5f1e","attributes":{"enabled":true,"created":1604627958,"updated":1604627958,"recoveryLevel":"Recoverable+Purgeable","recoverableDays":90}}'
headers:
cache-control:
- no-cache
content-length:
- '269'
content-type:
- application/json; charset=utf-8
date:
- Fri, 06 Nov 2020 01:59: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.58.0
x-powered-by:
- ASP.NET
status:
code: 200
message: OK
version: 1
62 changes: 62 additions & 0 deletions sdk/keyvault/azure-keyvault-secrets/tests/test_parse_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# -------------------------------------
import functools
from azure.keyvault.secrets import SecretClient, parse_key_vault_secret_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, SecretClient)


class TestParseId(KeyVaultTestCase):
@ResourceGroupPreparer(random_name_enabled=True)
@KeyVaultPreparer()
@KeyVaultClientPreparer()
def test_parse_secret_id_with_version(self, client):
secret_name = self.get_resource_name("secret")
secret_value = "secret_value"
# create secret
created_secret = client.set_secret(secret_name, secret_value)

# [START parse_key_vault_secret_id]
secret = client.get_secret(secret_name)
parsed_secret_id = parse_key_vault_secret_id(secret.id)

print(parsed_secret_id.name)
print(parsed_secret_id.vault_url)
print(parsed_secret_id.version)
print(parsed_secret_id.source_id)
# [END parse_key_vault_secret_id]
self.assertEqual(parsed_secret_id.name, secret_name)
self.assertEqual(parsed_secret_id.vault_url, client.vault_url)
self.assertEqual(parsed_secret_id.version, secret.properties.version)
self.assertEqual(parsed_secret_id.source_id, secret.id)

def test_parse_secret_id_with_pending_version(self):
source_id = "https://keyvault-name.vault.azure.net/secrets/secret-name/pending"
parsed_secret_id = parse_key_vault_secret_id(source_id)

self.assertEqual(parsed_secret_id.name, "secret-name")
self.assertEqual(parsed_secret_id.vault_url, "https://keyvault-name.vault.azure.net")
self.assertEqual(parsed_secret_id.version, "pending")
self.assertEqual(
parsed_secret_id.source_id,
"https://keyvault-name.vault.azure.net/secrets/secret-name/pending",
)

def test_parse_deleted_secret_id(self):
source_id = "https://keyvault-name.vault.azure.net/deletedsecrets/deleted-secret"
parsed_secret_id = parse_key_vault_secret_id(source_id)

self.assertEqual(parsed_secret_id.name, "deleted-secret")
self.assertEqual(parsed_secret_id.vault_url, "https://keyvault-name.vault.azure.net")
self.assertIsNone(parsed_secret_id.version)
self.assertEqual(
parsed_secret_id.source_id,
"https://keyvault-name.vault.azure.net/deletedsecrets/deleted-secret",
)