diff --git a/README.md b/README.md index af1e79f4..de6ce235 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The Skyflow Python SDK is designed to help with integrating Skyflow into a Pytho - [Generate bearer tokens with context](#generate-bearer-tokens-with-context) - [Generate scoped bearer tokens](#generate-scoped-bearer-tokens) - [Generate signed data tokens](#generate-signed-data-tokens) + - [Bearer token expiry edge case](#bearer-token-expiry-edge-case) - [Logging](#logging) - [Reporting a Vulnerability](#reporting-a-vulnerability) @@ -2142,6 +2143,130 @@ Notes: - The `time_to_live` (TTL) value should be specified in seconds. - By default, the TTL value is set to 60 seconds. +#### Bearer token expiry edge case +When you use bearer tokens for authentication and API requests in SDKs, there's the potential for a token to expire after the token is verified as valid but before the actual API call is made, causing the request to fail unexpectedly due to the token's expiration. An error from this edge case would look something like this: + +```txt +message: Authentication failed. Bearer token is expired. Use a valid bearer token. See https://docs.skyflow.com/api-authentication/ +``` + +If you encounter this kind of error, retry the request. During the retry, the SDK detects that the previous bearer token has expired and generates a new one for the current and subsequent requests. + +#### [Example](https://github.com/skyflowapi/skyflow-python/blob/v2/samples/service_account/bearer_token_expiry_example.py): +```python +import json +from skyflow.error import SkyflowError +from skyflow import Env +from skyflow import Skyflow, LogLevel +from skyflow.utils.enums import RedactionType +from skyflow.vault.tokens import DetokenizeRequest + +""" + * This example demonstrates how to configure and use the Skyflow SDK + * to detokenize sensitive data stored in a Skyflow vault. + * It includes setting up credentials, configuring the vault, and + * making a detokenization request. The code also implements a retry + * mechanism to handle unauthorized access errors (HTTP 401). +""" + + +def detokenize_data(skyflow_client, vault_id): + try: + # Creating a list of tokens to be detokenized + detokenize_data = [ + { + 'token': '', + 'redaction': RedactionType.REDACTED + }, + { + 'token': '', + 'redaction': RedactionType.MASKED + } + ] + + # Building a detokenization request + detokenize_request = DetokenizeRequest( + data=detokenize_data, + continue_on_error=False + ) + + # Sending the detokenization request and receiving the response + response = skyflow_client.vault(vault_id).detokenize(detokenize_request) + + # Printing the detokenized response + print('Detokenization successful:', response) + + except SkyflowError as error: + print("Skyflow error occurred:", error) + raise + + except Exception as error: + print("Unexpected error occurred:", error) + raise + + +def perform_detokenization(): + try: + # Setting up credentials for accessing the Skyflow vault + cred = { + 'clientID': '', + 'clientName': '', + 'tokenURI': '', + 'keyID': '', + 'privateKey': '', + } + + skyflow_credentials = { + 'credentials_string': json.dumps(cred) # Credentials string for authentication + } + + credentials = { + 'token': '' + } + + # Configuring the Skyflow vault with necessary details + primary_vault_config = { + 'vault_id': '', # Vault ID + 'cluster_id': '', # Cluster ID + 'env': Env.PROD, # Environment set to PROD + 'credentials': credentials # Setting credentials + } + + # Creating a Skyflow client instance with the configured vault + skyflow_client = ( + Skyflow.builder() + .add_vault_config(primary_vault_config) + .add_skyflow_credentials(skyflow_credentials) + .set_log_level(LogLevel.ERROR) # Setting log level to ERROR + .build() + ) + + # Attempting to detokenize data using the Skyflow client + try: + detokenize_data(skyflow_client, primary_vault_config.get('vault_id')) + except SkyflowError as err: + # Retry detokenization if the error is due to unauthorized access (HTTP 401) + if err.http_code == 401: + print("Unauthorized access detected. Retrying...") + detokenize_data(skyflow_client, primary_vault_config.get('vault_id')) + else: + # Rethrow the exception for other error codes + raise err + + except SkyflowError as error: + print('Skyflow Specific Error:', { + 'code': error.http_code, + 'message': error.message, + 'details': error.details + }) + except Exception as error: + print('Unexpected Error:', error) + + +# Invoke the function +perform_detokenization() +``` + ## Logging The SDK provides logging using python's inbuilt `logging` library. By default the logging level of the SDK is set to `LogLevel.ERROR`. This can be changed by using `set_log_level(log_level)` as shown below: diff --git a/samples/service_account/bearer_token_expiry_example.py b/samples/service_account/bearer_token_expiry_example.py new file mode 100644 index 00000000..169bf500 --- /dev/null +++ b/samples/service_account/bearer_token_expiry_example.py @@ -0,0 +1,111 @@ +import json +from skyflow.error import SkyflowError +from skyflow import Env +from skyflow import Skyflow, LogLevel +from skyflow.utils.enums import RedactionType +from skyflow.vault.tokens import DetokenizeRequest + +""" + * This example demonstrates how to configure and use the Skyflow SDK + * to detokenize sensitive data stored in a Skyflow vault. + * It includes setting up credentials, configuring the vault, and + * making a detokenization request. The code also implements a retry + * mechanism to handle unauthorized access errors (HTTP 401). +""" + + +def detokenize_data(skyflow_client, vault_id): + try: + # Creating a list of tokens to be detokenized + detokenize_data = [ + { + 'token': '', + 'redaction': RedactionType.REDACTED + }, + { + 'token': '', + 'redaction': RedactionType.MASKED + } + ] + + # Building a detokenization request + detokenize_request = DetokenizeRequest( + data=detokenize_data, + continue_on_error=False + ) + + # Sending the detokenization request and receiving the response + response = skyflow_client.vault(vault_id).detokenize(detokenize_request) + + # Printing the detokenized response + print('Detokenization successful:', response) + + except SkyflowError as error: + print("Skyflow error occurred:", error) + raise + + except Exception as error: + print("Unexpected error occurred:", error) + raise + + +def perform_detokenization(): + try: + # Setting up credentials for accessing the Skyflow vault + cred = { + 'clientID': '', + 'clientName': '', + 'tokenURI': '', + 'keyID': '', + 'privateKey': '', + } + + skyflow_credentials = { + 'credentials_string': json.dumps(cred) # Credentials string for authentication + } + + credentials = { + 'token': '' + } + + # Configuring the Skyflow vault with necessary details + primary_vault_config = { + 'vault_id': '', # Vault ID + 'cluster_id': '', # Cluster ID + 'env': Env.PROD, # Environment set to PROD + 'credentials': credentials # Setting credentials + } + + # Creating a Skyflow client instance with the configured vault + skyflow_client = ( + Skyflow.builder() + .add_vault_config(primary_vault_config) + .add_skyflow_credentials(skyflow_credentials) + .set_log_level(LogLevel.ERROR) # Setting log level to ERROR + .build() + ) + + # Attempting to detokenize data using the Skyflow client + try: + detokenize_data(skyflow_client, primary_vault_config.get('vault_id')) + except SkyflowError as err: + # Retry detokenization if the error is due to unauthorized access (HTTP 401) + if err.http_code == 401: + print("Unauthorized access detected. Retrying...") + detokenize_data(skyflow_client, primary_vault_config.get('vault_id')) + else: + # Rethrow the exception for other error codes + raise err + + except SkyflowError as error: + print('Skyflow Specific Error:', { + 'code': error.http_code, + 'message': error.message, + 'details': error.details + }) + except Exception as error: + print('Unexpected Error:', error) + + +# Invoke the function +perform_detokenization()