Credit Card Parameter (#903)

This commit is contained in:
Kerem Yilmaz
2024-10-03 16:18:21 -07:00
committed by GitHub
parent 83d0879034
commit b0aa181c68
11 changed files with 412 additions and 12 deletions

View File

@@ -203,7 +203,6 @@ class WorkflowRunContext:
except BitwardenBaseError as e:
LOG.error(f"Failed to get secret from Bitwarden. Error: {e}")
raise e
# TODO (kerem): Implement this
elif parameter.parameter_type == ParameterType.BITWARDEN_SENSITIVE_INFORMATION:
try:
# Get the Bitwarden login credentials from AWS secrets
@@ -256,6 +255,73 @@ class WorkflowRunContext:
except BitwardenBaseError as e:
LOG.error(f"Failed to get sensitive information from Bitwarden. Error: {e}")
raise e
elif parameter.parameter_type == ParameterType.BITWARDEN_CREDIT_CARD_DATA:
try:
# Get the Bitwarden login credentials from AWS secrets
client_id = await aws_client.get_secret(parameter.bitwarden_client_id_aws_secret_key)
client_secret = await aws_client.get_secret(parameter.bitwarden_client_secret_aws_secret_key)
master_password = await aws_client.get_secret(parameter.bitwarden_master_password_aws_secret_key)
except Exception as e:
LOG.error(f"Failed to get Bitwarden login credentials from AWS secrets. Error: {e}")
raise e
if self.has_parameter(parameter.bitwarden_item_id) and self.has_value(parameter.bitwarden_item_id):
item_id = self.values[parameter.bitwarden_item_id]
else:
item_id = parameter.bitwarden_item_id
if self.has_parameter(parameter.bitwarden_collection_id) and self.has_value(
parameter.bitwarden_collection_id
):
collection_id = self.values[parameter.bitwarden_collection_id]
else:
collection_id = parameter.bitwarden_collection_id
try:
credit_card_data = await BitwardenService.get_credit_card_data(
client_id,
client_secret,
master_password,
organization.bw_organization_id,
organization.bw_collection_ids,
collection_id,
item_id,
)
if not credit_card_data:
raise ValueError("Credit card data not found in Bitwarden")
self.secrets[BitwardenConstants.CLIENT_ID] = client_id
self.secrets[BitwardenConstants.CLIENT_SECRET] = client_secret
self.secrets[BitwardenConstants.MASTER_PASSWORD] = master_password
self.secrets[BitwardenConstants.ITEM_ID] = item_id
fields_to_obfuscate = {
BitwardenConstants.CREDIT_CARD_NUMBER: "card_number",
BitwardenConstants.CREDIT_CARD_CVV: "card_cvv",
}
pass_through_fields = {
BitwardenConstants.CREDIT_CARD_HOLDER_NAME: "card_holder_name",
BitwardenConstants.CREDIT_CARD_EXPIRATION_MONTH: "card_exp_month",
BitwardenConstants.CREDIT_CARD_EXPIRATION_YEAR: "card_exp_year",
BitwardenConstants.CREDIT_CARD_BRAND: "card_brand",
}
parameter_value: dict[str, Any] = {
field_name: credit_card_data[field_key] for field_key, field_name in pass_through_fields.items()
}
for data_key, secret_suffix in fields_to_obfuscate.items():
random_secret_id = self.generate_random_secret_id()
secret_id = f"{random_secret_id}_{secret_suffix}"
self.secrets[secret_id] = credit_card_data[data_key]
parameter_value[secret_suffix] = secret_id
self.values[parameter.key] = parameter_value
except BitwardenBaseError as e:
LOG.error(f"Failed to get credit card data from Bitwarden. Error: {e}")
raise e
elif isinstance(parameter, ContextParameter):
if isinstance(parameter.source, WorkflowParameter):
# TODO (kerem): set this while initializing the context manager

View File

@@ -4,7 +4,7 @@ from datetime import datetime
from enum import StrEnum
from typing import Annotated, Literal, Union
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field
class ParameterType(StrEnum):
@@ -13,6 +13,7 @@ class ParameterType(StrEnum):
AWS_SECRET = "aws_secret"
BITWARDEN_LOGIN_CREDENTIAL = "bitwarden_login_credential"
BITWARDEN_SENSITIVE_INFORMATION = "bitwarden_sensitive_information"
BITWARDEN_CREDIT_CARD_DATA = "bitwarden_credit_card_data"
OUTPUT = "output"
@@ -86,6 +87,25 @@ class BitwardenSensitiveInformationParameter(Parameter):
deleted_at: datetime | None = None
class BitwardenCreditCardDataParameter(Parameter):
model_config = ConfigDict(from_attributes=True)
parameter_type: Literal[ParameterType.BITWARDEN_CREDIT_CARD_DATA] = ParameterType.BITWARDEN_CREDIT_CARD_DATA
# parameter fields
bitwarden_credit_card_data_parameter_id: str
workflow_id: str
# bitwarden cli required fields
bitwarden_client_id_aws_secret_key: str
bitwarden_client_secret_aws_secret_key: str
bitwarden_master_password_aws_secret_key: str
# bitwarden ids for the credit card item
bitwarden_collection_id: str
bitwarden_item_id: str
created_at: datetime
modified_at: datetime
deleted_at: datetime | None = None
class WorkflowParameterType(StrEnum):
STRING = "string"
INTEGER = "integer"
@@ -150,6 +170,7 @@ ParameterSubclasses = Union[
AWSSecretParameter,
BitwardenLoginCredentialParameter,
BitwardenSensitiveInformationParameter,
BitwardenCreditCardDataParameter,
OutputParameter,
]
PARAMETER_TYPE = Annotated[ParameterSubclasses, Field(discriminator="parameter_type")]

View File

@@ -61,6 +61,21 @@ class BitwardenSensitiveInformationParameterYAML(ParameterYAML):
bitwarden_identity_fields: list[str]
class BitwardenCreditCardDataParameterYAML(ParameterYAML):
# There is a mypy bug with Literal. Without the type: ignore, mypy will raise an error:
# Parameter 1 of Literal[...] cannot be of type "Any"
# This pattern already works in block.py but since the ParameterType is not defined in this file, mypy is not able
# to infer the type of the parameter_type attribute.
parameter_type: Literal[ParameterType.BITWARDEN_CREDIT_CARD_DATA] = ParameterType.BITWARDEN_CREDIT_CARD_DATA # type: ignore
# bitwarden cli required fields
bitwarden_client_id_aws_secret_key: str
bitwarden_client_secret_aws_secret_key: str
bitwarden_master_password_aws_secret_key: str
# bitwarden ids for the credit card item
bitwarden_collection_id: str
bitwarden_item_id: str
class WorkflowParameterYAML(ParameterYAML):
# There is a mypy bug with Literal. Without the type: ignore, mypy will raise an error:
# Parameter 1 of Literal[...] cannot be of type "Any"
@@ -196,6 +211,7 @@ PARAMETER_YAML_SUBCLASSES = (
AWSSecretParameterYAML
| BitwardenLoginCredentialParameterYAML
| BitwardenSensitiveInformationParameterYAML
| BitwardenCreditCardDataParameterYAML
| WorkflowParameterYAML
| ContextParameterYAML
| OutputParameterYAML

View File

@@ -534,6 +534,28 @@ class WorkflowService:
description=description,
)
async def create_bitwarden_credit_card_data_parameter(
self,
workflow_id: str,
bitwarden_client_id_aws_secret_key: str,
bitwarden_client_secret_aws_secret_key: str,
bitwarden_master_password_aws_secret_key: str,
bitwarden_collection_id: str,
bitwarden_item_id: str,
key: str,
description: str | None = None,
) -> Parameter:
return await app.DATABASE.create_bitwarden_credit_card_data_parameter(
workflow_id=workflow_id,
bitwarden_client_id_aws_secret_key=bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=bitwarden_master_password_aws_secret_key,
bitwarden_collection_id=bitwarden_collection_id,
bitwarden_item_id=bitwarden_item_id,
key=key,
description=description,
)
async def create_output_parameter(
self, workflow_id: str, key: str, description: str | None = None
) -> OutputParameter:
@@ -824,10 +846,11 @@ class WorkflowService:
async def create_workflow_from_request(
self,
organization_id: str,
organization: Organization,
request: WorkflowCreateYAMLRequest,
workflow_permanent_id: str | None = None,
) -> Workflow:
organization_id = organization.organization_id
LOG.info(
"Creating workflow from request",
organization_id=organization_id,
@@ -938,6 +961,21 @@ class WorkflowService:
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_CREDIT_CARD_DATA:
if not organization.bw_organization_id and not organization.bw_collection_ids:
raise InvalidWorkflowDefinition(
message="To use credit card data parameters, please contact us at support@skyvern.com"
)
parameters[parameter.key] = await self.create_bitwarden_credit_card_data_parameter(
workflow_id=workflow.workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
bitwarden_collection_id=parameter.bitwarden_collection_id,
bitwarden_item_id=parameter.bitwarden_item_id,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.WORKFLOW:
parameters[parameter.key] = await self.create_workflow_parameter(
workflow_id=workflow.workflow_id,