Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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,29 @@
# ------------------------------------
# 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,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 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 23:51:37 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/0fb32b11fdbf47eb9973e04a064a5b3f","attributes":{"enabled":true,"created":1604706698,"updated":1604706698,"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 23:51:38 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/0fb32b11fdbf47eb9973e04a064a5b3f","attributes":{"enabled":true,"created":1604706698,"updated":1604706698,"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 23:51:38 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
54 changes: 54 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,54 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# -------------------------------------
from azure.keyvault.secrets import SecretClient, parse_key_vault_secret_id
from devtools_testutils import ResourceGroupPreparer, KeyVaultPreparer

from _shared.preparer import KeyVaultClientPreparer
from _shared.test_case import KeyVaultTestCase


class TestParseId(KeyVaultTestCase):
@ResourceGroupPreparer(random_name_enabled=True)
@KeyVaultPreparer()
@KeyVaultClientPreparer(SecretClient)
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]
assert parsed_secret_id.name == secret_name
assert parsed_secret_id.vault_url == client.vault_url
assert parsed_secret_id.version == secret.properties.version
assert parsed_secret_id.source_id == secret.id


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

assert parsed_secret_id.name == "secret-name"
assert parsed_secret_id.vault_url == "https://keyvault-name.vault.azure.net"
assert parsed_secret_id.version == "pending"
assert parsed_secret_id.source_id == "https://keyvault-name.vault.azure.net/secrets/secret-name/pending"


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

assert parsed_secret_id.name == "deleted-secret"
assert parsed_secret_id.vault_url == "https://keyvault-name.vault.azure.net"
assert parsed_secret_id.version is None
assert parsed_secret_id.source_id == "https://keyvault-name.vault.azure.net/deletedsecrets/deleted-secret"