From fee108bbdc3d9bd72174b91699c9c7ce4e597ccd Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Jun 2025 22:01:07 +0300 Subject: [PATCH 1/6] keyvault --- src/sempy_labs/_helper_functions.py | 14 ++- src/sempy_labs/key_vault/__init__.py | 16 +++ src/sempy_labs/key_vault/_secrets.py | 150 +++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 src/sempy_labs/key_vault/__init__.py create mode 100644 src/sempy_labs/key_vault/_secrets.py diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index d28b483f..6d5867ef 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -1929,12 +1929,12 @@ def get_token(audience="pbi"): elif client == "fabric_sp": token = auth.token_provider.get() or get_token c = fabric.FabricRestClient(token_provider=token) - elif client in ["azure", "graph"]: + elif client in ["azure", "graph", "keyvault"]: pass else: raise ValueError(f"{icons.red_dot} The '{client}' client is not supported.") - if client not in ["azure", "graph"]: + if client not in ["azure", "graph", "keyvault"]: if method == "get": response = c.get(request) elif method == "delete": @@ -1947,7 +1947,7 @@ def get_token(audience="pbi"): response = c.put(request, json=payload) else: raise NotImplementedError - else: + elif client in ["azure", "graph"]: headers = _get_headers(auth.token_provider.get(), audience=client) if client == "graph": url = f"https://graph.microsoft.com/v1.0/{request}" @@ -1961,6 +1961,14 @@ def get_token(audience="pbi"): headers=headers, json=payload, ) + elif client == "keyvault": + token = notebookutils.credentials.getToken("keyvault") + headers = {"Authorization": f"Bearer {token}"} + response = requests.request( + method.upper(), f"{url}?api-version=7.4", headers=headers, json=payload + ) + else: + raise NotImplementedError if lro_return_json: return lro(c, response, status_codes).json() diff --git a/src/sempy_labs/key_vault/__init__.py b/src/sempy_labs/key_vault/__init__.py new file mode 100644 index 00000000..9414f07e --- /dev/null +++ b/src/sempy_labs/key_vault/__init__.py @@ -0,0 +1,16 @@ +from ._secrets import ( + get_secret, + set_secret, + delete_secret, + list_secrets, + recover_deleted_secret, +) + + +__all__ = [ + "get_secret", + "set_secret", + "delete_secret", + "list_secrets", + "recover_deleted_secret", +] \ No newline at end of file diff --git a/src/sempy_labs/key_vault/_secrets.py b/src/sempy_labs/key_vault/_secrets.py new file mode 100644 index 00000000..ef872016 --- /dev/null +++ b/src/sempy_labs/key_vault/_secrets.py @@ -0,0 +1,150 @@ +from typing import Any +import pandas as pd +from sempy_labs._helper_functions import ( + _base_api, +) +import sempy_labs._icons as icons +from sempy._utils._log import log + + +@log +def get_secret(key_vault_uri: str, secret_name: str) -> Any: + """ + Retrieves the latest version of a secret from the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Secret - Get Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret in the Key Vault. + + Returns + ------- + Any + The value of the latest version of the secret. + """ + + response = _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}/versions", client="keyvault" + ) + url = response.json().get("value")[-1].get("id") + response = _base_api(request=f"{url}", client="keyvault") + return response.json().get("value") + + +@log +def set_secret(key_vault_uri: str, secret_name: str, secret_value: Any): + """ + Sets a secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Set Secret - Set Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be set in the Key Vault. + secret_value : Any + Value of the secret to be set in the Key Vault. + """ + + payload = {"value": secret_value} + _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}", + client="keyvault", + method="put", + payload=payload, + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully set within the '{key_vault_uri}' Key Vault." + ) + + +@log +def list_secrets(key_vault_uri: str) -> pd.DataFrame: + """ + Lists all secrets in the specified Azure Key Vault. + + This is a wrapper function for the following API: `List Secrets - List Secrets `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + + Returns + ------- + pandas.DataFrame + A pandas DataFrame containing details of all secrets in the Key Vault. + """ + + response = _base_api(request=f"{key_vault_uri}/secrets", client="keyvault") + df = pd.json_normalize(response.json().get("value")) + df.rename( + columns={ + "id": "Secret Id", + "attributes.enabled": "Enabled", + "attributes.created": "Created Date", + "attributes.updated": "Updated Date", + "attributes.recoveryLevel": "Recovery Level", + "attributes.recoverableDays": "Recoverable Days", + }, + inplace=True, + ) + df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") + df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + return df + + +@log +def recover_deleted_secret(key_vault_uri: str, secret_name: str): + """ + Recovers a deleted secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Recover Deleted Secret - Recover Deleted Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the deleted secret to be recovered in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/deletedsecrets/{secret_name}/recover", + client="keyvault", + method="post", + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully recovered within the '{key_vault_uri}' Key Vault." + ) + + +@log +def delete_secret(key_vault_uri: str, secret_name: str): + """ + Deletes a secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Delete Secret - Delete Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be deleted in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}", + client="keyvault", + method="delete", + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully deleted within the '{key_vault_uri}' Key Vault." + ) From bd23052eebe1aa6e7460963df09eecb10d39cac6 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 18 Jun 2025 22:59:49 +0300 Subject: [PATCH 2/6] fix secrets --- src/sempy_labs/_helper_functions.py | 2 +- .../{key_vault => keyvault}/__init__.py | 2 +- .../{key_vault => keyvault}/_secrets.py | 96 ++++++++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) rename src/sempy_labs/{key_vault => keyvault}/__init__.py (99%) rename src/sempy_labs/{key_vault => keyvault}/_secrets.py (59%) diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index 6d5867ef..2630c131 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -1965,7 +1965,7 @@ def get_token(audience="pbi"): token = notebookutils.credentials.getToken("keyvault") headers = {"Authorization": f"Bearer {token}"} response = requests.request( - method.upper(), f"{url}?api-version=7.4", headers=headers, json=payload + method.upper(), f"{request}?api-version=7.4", headers=headers, json=payload ) else: raise NotImplementedError diff --git a/src/sempy_labs/key_vault/__init__.py b/src/sempy_labs/keyvault/__init__.py similarity index 99% rename from src/sempy_labs/key_vault/__init__.py rename to src/sempy_labs/keyvault/__init__.py index 9414f07e..92c17a6e 100644 --- a/src/sempy_labs/key_vault/__init__.py +++ b/src/sempy_labs/keyvault/__init__.py @@ -13,4 +13,4 @@ "delete_secret", "list_secrets", "recover_deleted_secret", -] \ No newline at end of file +] diff --git a/src/sempy_labs/key_vault/_secrets.py b/src/sempy_labs/keyvault/_secrets.py similarity index 59% rename from src/sempy_labs/key_vault/_secrets.py rename to src/sempy_labs/keyvault/_secrets.py index ef872016..52ba9d6b 100644 --- a/src/sempy_labs/key_vault/_secrets.py +++ b/src/sempy_labs/keyvault/_secrets.py @@ -2,6 +2,7 @@ import pandas as pd from sempy_labs._helper_functions import ( _base_api, + _create_dataframe, ) import sempy_labs._icons as icons from sempy._utils._log import log @@ -69,7 +70,7 @@ def list_secrets(key_vault_uri: str) -> pd.DataFrame: """ Lists all secrets in the specified Azure Key Vault. - This is a wrapper function for the following API: `List Secrets - List Secrets `_. + This is a wrapper function for the following API: `Get Secrets - Get Secrets `_. Parameters ---------- @@ -82,8 +83,30 @@ def list_secrets(key_vault_uri: str) -> pd.DataFrame: A pandas DataFrame containing details of all secrets in the Key Vault. """ - response = _base_api(request=f"{key_vault_uri}/secrets", client="keyvault") - df = pd.json_normalize(response.json().get("value")) + columns = { + "Secret Id": "str", + "Enabled": "bool", + "Created Date": "datetime", + "Updated Date": "datetime", + "Recovery Level": "str", + "Recoverable Days": "int", + } + + df = _create_dataframe(columns=columns) + + all_items = [] + url = f"{key_vault_uri}/secrets" + while url: + response = _base_api(request=url, client="keyvault") + result = response.json() + items = result.get("value", []) + all_items.extend(items) + url = result.get("nextLink") # Update to next page if it exists + + if not all_items: + return df + + df = pd.json_normalize(all_items) df.rename( columns={ "id": "Secret Id", @@ -100,6 +123,73 @@ def list_secrets(key_vault_uri: str) -> pd.DataFrame: return df +@log +def list_deleted_secrets(key_vault_uri: str) -> pd.DataFrame: + """ + Lists all deleted secrets in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Deleted Secrets - GetDeleted Secrets `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + + Returns + ------- + pandas.DataFrame + A pandas DataFrame containing details of all secrets in the Key Vault. + """ + + columns = { + "Recovery Id": "str", + "Deleted Date": "datetime", + "Scheduled Purge Date": "datetime", + "Content Type": "str", + "Secret Id": "str", + "Enabled": "bool", + "Created Date": "datetime", + "Updated Date": "datetime", + "Recovery Level": "str", + } + + df = _create_dataframe(columns=columns) + + all_items = [] + url = f"{key_vault_uri}/deletedSecrets" + while url: + response = _base_api(request=url, client="keyvault") + result = response.json() + items = result.get("value", []) + all_items.extend(items) + url = result.get("nextLink") # Update to next page if it exists + + if not all_items: + return df + + df = pd.json_normalize(all_items) + df.rename( + columns={ + "recoveryId": "Recovery Id", + "deletedDate": "Deleted Date", + "scheduledPurgeDate": "Scheduled Purge Date", + "contentType": "Content Type", + "id": "Secret Id", + "attributes.enabled": "Enabled", + "attributes.created": "Created Date", + "attributes.updated": "Updated Date", + "attributes.recoveryLevel": "Recovery Level", + }, + inplace=True, + ) + df["Scheduled Purge Date"] = pd.to_datetime(df["Scheduled Purge Date"], unit="s") + df["Deleted Date"] = pd.to_datetime(df["Deleted Date"], unit="s") + df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") + df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + + return df + + @log def recover_deleted_secret(key_vault_uri: str, secret_name: str): """ From 8b316270342740064a3b6ab9ff8369881ebf6117 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 20 Jun 2025 11:10:35 +0300 Subject: [PATCH 3/6] purge deleted secret --- src/sempy_labs/keyvault/__init__.py | 2 ++ src/sempy_labs/keyvault/_secrets.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/sempy_labs/keyvault/__init__.py b/src/sempy_labs/keyvault/__init__.py index 92c17a6e..54086b84 100644 --- a/src/sempy_labs/keyvault/__init__.py +++ b/src/sempy_labs/keyvault/__init__.py @@ -4,6 +4,7 @@ delete_secret, list_secrets, recover_deleted_secret, + purge_deleted_secret, ) @@ -13,4 +14,5 @@ "delete_secret", "list_secrets", "recover_deleted_secret", + "purge_deleted_secret", ] diff --git a/src/sempy_labs/keyvault/_secrets.py b/src/sempy_labs/keyvault/_secrets.py index 52ba9d6b..fd005616 100644 --- a/src/sempy_labs/keyvault/_secrets.py +++ b/src/sempy_labs/keyvault/_secrets.py @@ -211,7 +211,34 @@ def recover_deleted_secret(key_vault_uri: str, secret_name: str): method="post", ) print( - f"{icons.green_dot} The '{secret_name}' secret has been successfully recovered within the '{key_vault_uri}' Key Vault." + f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully recovered." + ) + + +@log +def purge_deleted_secret(key_vault_uri: str, secret_name: str): + """ + Permanently deletes the specified secret. + The purge deleted secret operation removes the secret permanently, without the possibility of recovery. This operation can only be enabled on a soft-delete enabled vault. This operation requires the secrets/purge permission. + + This is a wrapper function for the following API: `Purge Deleted Secret - Purge Deleted Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the deleted secret to be recovered in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/deletedsecrets/{secret_name}", + client="keyvault", + method="delete", + status_codes=204, + ) + print( + f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully purged." ) From a1c36fb028f8f7c11eebec4a090009d219906b15 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 5 Nov 2025 08:36:42 +0200 Subject: [PATCH 4/6] fix version --- src/sempy_labs/_helper_functions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index 2630c131..2c2b1f60 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -1965,7 +1965,10 @@ def get_token(audience="pbi"): token = notebookutils.credentials.getToken("keyvault") headers = {"Authorization": f"Bearer {token}"} response = requests.request( - method.upper(), f"{request}?api-version=7.4", headers=headers, json=payload + method.upper(), + f"{request}?api-version=2025-07-01", + headers=headers, + json=payload, ) else: raise NotImplementedError From 1865cff1a710b845163c8eac9c7dc1f443c3a28b Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 5 Nov 2025 08:37:57 +0200 Subject: [PATCH 5/6] int --- src/sempy_labs/keyvault/_secrets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sempy_labs/keyvault/_secrets.py b/src/sempy_labs/keyvault/_secrets.py index fd005616..f1084f06 100644 --- a/src/sempy_labs/keyvault/_secrets.py +++ b/src/sempy_labs/keyvault/_secrets.py @@ -120,6 +120,8 @@ def list_secrets(key_vault_uri: str) -> pd.DataFrame: ) df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + df["Recoverable Days"] = df["Recoverable Days"].astype(int) + return df From cbc9ed57cef5a3d3e866fc411d6f3e7d5dcc0bf0 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 5 Nov 2025 12:17:05 +0200 Subject: [PATCH 6/6] list_secret_versions --- src/sempy_labs/_helper_functions.py | 26 +- .../{keyvault => key_vault}/__init__.py | 10 + src/sempy_labs/key_vault/_secrets.py | 416 ++++++++++++++++++ src/sempy_labs/keyvault/_secrets.py | 269 ----------- 4 files changed, 446 insertions(+), 275 deletions(-) rename src/sempy_labs/{keyvault => key_vault}/__init__.py (56%) create mode 100644 src/sempy_labs/key_vault/_secrets.py delete mode 100644 src/sempy_labs/keyvault/_secrets.py diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index 2c2b1f60..16d8a22b 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -1964,12 +1964,26 @@ def get_token(audience="pbi"): elif client == "keyvault": token = notebookutils.credentials.getToken("keyvault") headers = {"Authorization": f"Bearer {token}"} - response = requests.request( - method.upper(), - f"{request}?api-version=2025-07-01", - headers=headers, - json=payload, - ) + + api_suffix = "?api-version=2025-07-01" + + if uses_pagination: + all_items = [] + while request: + if not request.endswith(api_suffix): + request += api_suffix + response = requests.request( + method.upper(), request, headers=headers, json=payload + ) + result = response.json() + items = result.get("value", []) + all_items.extend(items) + request = result.get("nextLink") # Update to next page if it exists + return all_items + else: + return requests.request( + method.upper(), f"{request}{api_suffix}", headers=headers, json=payload + ) else: raise NotImplementedError diff --git a/src/sempy_labs/keyvault/__init__.py b/src/sempy_labs/key_vault/__init__.py similarity index 56% rename from src/sempy_labs/keyvault/__init__.py rename to src/sempy_labs/key_vault/__init__.py index 54086b84..dcfcb072 100644 --- a/src/sempy_labs/keyvault/__init__.py +++ b/src/sempy_labs/key_vault/__init__.py @@ -5,6 +5,11 @@ list_secrets, recover_deleted_secret, purge_deleted_secret, + backup_secret, + restore_secret, + update_secret, + list_deleted_secrets, + list_secret_versions, ) @@ -15,4 +20,9 @@ "list_secrets", "recover_deleted_secret", "purge_deleted_secret", + "backup_secret", + "restore_secret", + "update_secret", + "list_deleted_secrets", + "list_secret_versions", ] diff --git a/src/sempy_labs/key_vault/_secrets.py b/src/sempy_labs/key_vault/_secrets.py new file mode 100644 index 00000000..27014779 --- /dev/null +++ b/src/sempy_labs/key_vault/_secrets.py @@ -0,0 +1,416 @@ +from typing import Any +import pandas as pd +from sempy_labs._helper_functions import ( + _base_api, + _create_dataframe, +) +import sempy_labs._icons as icons +from sempy._utils._log import log + + +@log +def get_secret(key_vault_uri: str, secret_name: str) -> Any: + """ + Retrieves the latest version of a secret from the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Secret - Get Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret in the Key Vault. + + Returns + ------- + Any + The value of the latest version of the secret. + """ + + response = _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}/versions", client="keyvault" + ) + url = response.json().get("value")[-1].get("id") + response = _base_api(request=f"{url}", client="keyvault") + return response.json().get("value") + + +@log +def set_secret(key_vault_uri: str, secret_name: str, secret_value: Any): + """ + Sets a secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Set Secret - Set Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be set in the Key Vault. + secret_value : Any + Value of the secret to be set in the Key Vault. + """ + + payload = {"value": secret_value} + _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}", + client="keyvault", + method="put", + payload=payload, + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully set within the '{key_vault_uri}' Key Vault." + ) + + +@log +def list_secrets(key_vault_uri: str) -> pd.DataFrame: + """ + Lists all secrets in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Secrets - Get Secrets `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + + Returns + ------- + pandas.DataFrame + A pandas DataFrame containing details of all secrets in the Key Vault. + """ + + columns = { + "Secret Id": "str", + "Enabled": "bool", + "Created Date": "datetime", + "Updated Date": "datetime", + "Recovery Level": "str", + "Recoverable Days": "int", + } + + df = _create_dataframe(columns=columns) + + responses = _base_api( + request=f"{key_vault_uri}/secrets", client="keyvault", uses_pagination=True + ) + + rows = [] + for r in responses: + rows.append( + { + "Secret Id": r.get("id"), + "Enabled": r.get("attributes", {}).get("enabled"), + "Created Date": r.get("attributes", {}).get("created"), + "Updated Date": r.get("attributes", {}).get("updated"), + "Recovery Level": r.get("attributes", {}).get("recoveryLevel"), + "Recoverable Days": r.get("attributes", {}).get("recoverableDays"), + } + ) + + if rows: + df = pd.DataFrame(rows, columns=list(columns.keys())) + df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") + df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + df["Recoverable Days"] = df["Recoverable Days"].astype(int) + + return df + + +@log +def list_secret_versions(key_vault_uri: str, secret_name: str) -> pd.DataFrame: + """ + Lists all versions of a specific secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Secret Versions - Get Secret Versions `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret. + + Returns + ------- + pandas.DataFrame + A pandas DataFrame containing details of all versions of the secret in the Key Vault. + """ + + columns = { + "Secret Id": "str", + "Enabled": "bool", + "Created Date": "datetime", + "Updated Date": "datetime", + } + + df = _create_dataframe(columns=columns) + + url = f"{key_vault_uri}/secrets/{secret_name}/versions" + responses = _base_api(request=url, client="keyvault", uses_pagination=True) + + rows = [] + for r in responses: + rows.append( + { + "Secret Id": r.get("id"), + "Enabled": r.get("attributes", {}).get("enabled"), + "Created Date": r.get("attributes", {}).get("created"), + "Updated Date": r.get("attributes", {}).get("updated"), + } + ) + + if rows: + df = pd.DataFrame(rows, columns=list(columns.keys())) + df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") + df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + + return df + + +@log +def list_deleted_secrets(key_vault_uri: str) -> pd.DataFrame: + """ + Lists all deleted secrets in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Get Deleted Secrets - GetDeleted Secrets `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + + Returns + ------- + pandas.DataFrame + A pandas DataFrame containing details of all secrets in the Key Vault. + """ + + columns = { + "Recovery Id": "str", + "Deleted Date": "datetime", + "Scheduled Purge Date": "datetime", + "Content Type": "str", + "Secret Id": "str", + "Enabled": "bool", + "Created Date": "datetime", + "Updated Date": "datetime", + "Recovery Level": "str", + } + + df = _create_dataframe(columns=columns) + + responses = _base_api( + request=f"{key_vault_uri}/deletedSecrets", + client="keyvault", + uses_pagination=True, + ) + + rows = [] + for r in responses: + rows.append( + { + "Recovery Id": r.get("recoveryId"), + "Deleted Date": r.get("deletedDate"), + "Scheduled Purge Date": r.get("scheduledPurgeDate"), + "Content Type": r.get("contentType"), + "Secret Id": r.get("id"), + "Enabled": r.get("attributes", {}).get("enabled"), + "Created Date": r.get("attributes", {}).get("created"), + "Updated Date": r.get("attributes", {}).get("updated"), + "Recovery Level": r.get("attributes", {}).get("recoveryLevel"), + } + ) + + if rows: + df = pd.DataFrame(rows, columns=list(columns.keys())) + df["Scheduled Purge Date"] = pd.to_datetime( + df["Scheduled Purge Date"], unit="s" + ) + df["Deleted Date"] = pd.to_datetime(df["Deleted Date"], unit="s") + df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") + df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") + + return df + + +@log +def recover_deleted_secret(key_vault_uri: str, secret_name: str): + """ + Recovers a deleted secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Recover Deleted Secret - Recover Deleted Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the deleted secret to be recovered in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/deletedsecrets/{secret_name}/recover", + client="keyvault", + method="post", + ) + print( + f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully recovered." + ) + + +@log +def purge_deleted_secret(key_vault_uri: str, secret_name: str): + """ + Permanently deletes the specified secret. + The purge deleted secret operation removes the secret permanently, without the possibility of recovery. This operation can only be enabled on a soft-delete enabled vault. This operation requires the secrets/purge permission. + + This is a wrapper function for the following API: `Purge Deleted Secret - Purge Deleted Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the deleted secret to be recovered in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/deletedsecrets/{secret_name}", + client="keyvault", + method="delete", + status_codes=204, + ) + print( + f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully purged." + ) + + +@log +def delete_secret(key_vault_uri: str, secret_name: str): + """ + Deletes a secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Delete Secret - Delete Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be deleted in the Key Vault. + """ + + _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}", + client="keyvault", + method="delete", + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully deleted within the '{key_vault_uri}' Key Vault." + ) + + +@log +def backup_secret(key_vault_uri: str, secret_name: str) -> bytes: + """ + Backs up a secret from the specified Azure Key Vault. + + This is a wrapper function for the following API: `Backup Secret - Backup Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be backed up in the Key Vault. + + Returns + ------- + bytes + The backup blob of the secret. + """ + + response = _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}/backup", client="keyvault" + ) + return response.json().get("value") + + +@log +def restore_secret(key_vault_uri: str, backup_blob: bytes) -> dict: + """ + Restores a backed up secret to the specified Azure Key Vault. + + This is a wrapper function for the following API: `Restore Secret - Restore Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + backup_blob : bytes + The backup blob of the secret to be restored. + + Returns + ------- + dict + The restored secret details. + """ + + payload = {"value": backup_blob} + response = _base_api( + request=f"{key_vault_uri}/secrets/restore", + client="keyvault", + method="post", + payload=payload, + ) + + print( + f"{icons.green_dot} The secret has been successfully restored within the '{key_vault_uri}' Key Vault." + ) + + return response.json() + + +@log +def update_secret( + key_vault_uri: str, secret_name: str, secret_version: str, enabled: bool = None +) -> dict: + """ + Updates the attributes of a secret in the specified Azure Key Vault. + + This is a wrapper function for the following API: `Update Secret - Update Secret `_. + + Parameters + ---------- + key_vault_uri : str + Azure Key Vault URI. + secret_name : str + Name of the secret to be updated in the Key Vault. + secret_version : str + Version of the secret to be updated in the Key Vault. + enabled : bool, optional + Specifies whether the secret is enabled or disabled. + + Returns + ------- + dict + The updated secret attributes. + """ + + payload = {"attributes": {"enabled": enabled}} + + response = _base_api( + request=f"{key_vault_uri}/secrets/{secret_name}/{secret_version}", + client="keyvault", + method="patch", + payload=payload, + ) + print( + f"{icons.green_dot} The '{secret_name}' secret has been successfully updated within the '{key_vault_uri}' Key Vault." + ) + + return response.json() diff --git a/src/sempy_labs/keyvault/_secrets.py b/src/sempy_labs/keyvault/_secrets.py deleted file mode 100644 index f1084f06..00000000 --- a/src/sempy_labs/keyvault/_secrets.py +++ /dev/null @@ -1,269 +0,0 @@ -from typing import Any -import pandas as pd -from sempy_labs._helper_functions import ( - _base_api, - _create_dataframe, -) -import sempy_labs._icons as icons -from sempy._utils._log import log - - -@log -def get_secret(key_vault_uri: str, secret_name: str) -> Any: - """ - Retrieves the latest version of a secret from the specified Azure Key Vault. - - This is a wrapper function for the following API: `Get Secret - Get Secret `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - secret_name : str - Name of the secret in the Key Vault. - - Returns - ------- - Any - The value of the latest version of the secret. - """ - - response = _base_api( - request=f"{key_vault_uri}/secrets/{secret_name}/versions", client="keyvault" - ) - url = response.json().get("value")[-1].get("id") - response = _base_api(request=f"{url}", client="keyvault") - return response.json().get("value") - - -@log -def set_secret(key_vault_uri: str, secret_name: str, secret_value: Any): - """ - Sets a secret in the specified Azure Key Vault. - - This is a wrapper function for the following API: `Set Secret - Set Secret `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - secret_name : str - Name of the secret to be set in the Key Vault. - secret_value : Any - Value of the secret to be set in the Key Vault. - """ - - payload = {"value": secret_value} - _base_api( - request=f"{key_vault_uri}/secrets/{secret_name}", - client="keyvault", - method="put", - payload=payload, - ) - print( - f"{icons.green_dot} The '{secret_name}' secret has been successfully set within the '{key_vault_uri}' Key Vault." - ) - - -@log -def list_secrets(key_vault_uri: str) -> pd.DataFrame: - """ - Lists all secrets in the specified Azure Key Vault. - - This is a wrapper function for the following API: `Get Secrets - Get Secrets `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - - Returns - ------- - pandas.DataFrame - A pandas DataFrame containing details of all secrets in the Key Vault. - """ - - columns = { - "Secret Id": "str", - "Enabled": "bool", - "Created Date": "datetime", - "Updated Date": "datetime", - "Recovery Level": "str", - "Recoverable Days": "int", - } - - df = _create_dataframe(columns=columns) - - all_items = [] - url = f"{key_vault_uri}/secrets" - while url: - response = _base_api(request=url, client="keyvault") - result = response.json() - items = result.get("value", []) - all_items.extend(items) - url = result.get("nextLink") # Update to next page if it exists - - if not all_items: - return df - - df = pd.json_normalize(all_items) - df.rename( - columns={ - "id": "Secret Id", - "attributes.enabled": "Enabled", - "attributes.created": "Created Date", - "attributes.updated": "Updated Date", - "attributes.recoveryLevel": "Recovery Level", - "attributes.recoverableDays": "Recoverable Days", - }, - inplace=True, - ) - df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") - df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") - df["Recoverable Days"] = df["Recoverable Days"].astype(int) - - return df - - -@log -def list_deleted_secrets(key_vault_uri: str) -> pd.DataFrame: - """ - Lists all deleted secrets in the specified Azure Key Vault. - - This is a wrapper function for the following API: `Get Deleted Secrets - GetDeleted Secrets `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - - Returns - ------- - pandas.DataFrame - A pandas DataFrame containing details of all secrets in the Key Vault. - """ - - columns = { - "Recovery Id": "str", - "Deleted Date": "datetime", - "Scheduled Purge Date": "datetime", - "Content Type": "str", - "Secret Id": "str", - "Enabled": "bool", - "Created Date": "datetime", - "Updated Date": "datetime", - "Recovery Level": "str", - } - - df = _create_dataframe(columns=columns) - - all_items = [] - url = f"{key_vault_uri}/deletedSecrets" - while url: - response = _base_api(request=url, client="keyvault") - result = response.json() - items = result.get("value", []) - all_items.extend(items) - url = result.get("nextLink") # Update to next page if it exists - - if not all_items: - return df - - df = pd.json_normalize(all_items) - df.rename( - columns={ - "recoveryId": "Recovery Id", - "deletedDate": "Deleted Date", - "scheduledPurgeDate": "Scheduled Purge Date", - "contentType": "Content Type", - "id": "Secret Id", - "attributes.enabled": "Enabled", - "attributes.created": "Created Date", - "attributes.updated": "Updated Date", - "attributes.recoveryLevel": "Recovery Level", - }, - inplace=True, - ) - df["Scheduled Purge Date"] = pd.to_datetime(df["Scheduled Purge Date"], unit="s") - df["Deleted Date"] = pd.to_datetime(df["Deleted Date"], unit="s") - df["Created Date"] = pd.to_datetime(df["Created Date"], unit="s") - df["Updated Date"] = pd.to_datetime(df["Updated Date"], unit="s") - - return df - - -@log -def recover_deleted_secret(key_vault_uri: str, secret_name: str): - """ - Recovers a deleted secret in the specified Azure Key Vault. - - This is a wrapper function for the following API: `Recover Deleted Secret - Recover Deleted Secret `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - secret_name : str - Name of the deleted secret to be recovered in the Key Vault. - """ - - _base_api( - request=f"{key_vault_uri}/deletedsecrets/{secret_name}/recover", - client="keyvault", - method="post", - ) - print( - f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully recovered." - ) - - -@log -def purge_deleted_secret(key_vault_uri: str, secret_name: str): - """ - Permanently deletes the specified secret. - The purge deleted secret operation removes the secret permanently, without the possibility of recovery. This operation can only be enabled on a soft-delete enabled vault. This operation requires the secrets/purge permission. - - This is a wrapper function for the following API: `Purge Deleted Secret - Purge Deleted Secret `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - secret_name : str - Name of the deleted secret to be recovered in the Key Vault. - """ - - _base_api( - request=f"{key_vault_uri}/deletedsecrets/{secret_name}", - client="keyvault", - method="delete", - status_codes=204, - ) - print( - f"{icons.green_dot} The '{secret_name}' secret within the '{key_vault_uri}' Key Vault has been successfully purged." - ) - - -@log -def delete_secret(key_vault_uri: str, secret_name: str): - """ - Deletes a secret in the specified Azure Key Vault. - - This is a wrapper function for the following API: `Delete Secret - Delete Secret `_. - - Parameters - ---------- - key_vault_uri : str - Azure Key Vault URI. - secret_name : str - Name of the secret to be deleted in the Key Vault. - """ - - _base_api( - request=f"{key_vault_uri}/secrets/{secret_name}", - client="keyvault", - method="delete", - ) - print( - f"{icons.green_dot} The '{secret_name}' secret has been successfully deleted within the '{key_vault_uri}' Key Vault." - )