diff --git a/alembic/versions/2024_07_11_1644-ac679ea03578_create_bitwarden_identity_parameter_.py b/alembic/versions/2024_07_11_1644-ac679ea03578_create_bitwarden_identity_parameter_.py new file mode 100644 index 00000000..68f4f561 --- /dev/null +++ b/alembic/versions/2024_07_11_1644-ac679ea03578_create_bitwarden_identity_parameter_.py @@ -0,0 +1,71 @@ +"""Create bitwarden identity parameter table + +Revision ID: ac679ea03578 +Revises: bea545cb21b4 +Create Date: 2024-07-11 16:44:54.145819+00:00 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "ac679ea03578" +down_revision: Union[str, None] = "bea545cb21b4" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "bitwarden_sensitive_information_parameters", + sa.Column("bitwarden_sensitive_information_parameter_id", sa.String(), nullable=False), + sa.Column("workflow_id", sa.String(), nullable=False), + sa.Column("key", sa.String(), nullable=False), + sa.Column("description", sa.String(), nullable=True), + sa.Column("bitwarden_client_id_aws_secret_key", sa.String(), nullable=False), + sa.Column("bitwarden_client_secret_aws_secret_key", sa.String(), nullable=False), + sa.Column("bitwarden_master_password_aws_secret_key", sa.String(), nullable=False), + sa.Column("bitwarden_collection_id", sa.String(), nullable=False), + sa.Column("bitwarden_identity_key", sa.String(), nullable=False), + sa.Column("bitwarden_identity_fields", sa.JSON(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("modified_at", sa.DateTime(), nullable=False), + sa.Column("deleted_at", sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint( + ["workflow_id"], + ["workflows.workflow_id"], + ), + sa.PrimaryKeyConstraint("bitwarden_sensitive_information_parameter_id"), + ) + op.create_index( + op.f("ix_bitwarden_sensitive_information_parameters_bitwarden_sensitive_information_parameter_id"), + "bitwarden_sensitive_information_parameters", + ["bitwarden_sensitive_information_parameter_id"], + unique=False, + ) + op.create_index( + op.f("ix_bitwarden_sensitive_information_parameters_workflow_id"), + "bitwarden_sensitive_information_parameters", + ["workflow_id"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + op.f("ix_bitwarden_sensitive_information_parameters_workflow_id"), + table_name="bitwarden_sensitive_information_parameters", + ) + op.drop_index( + op.f("ix_bitwarden_sensitive_information_parameters_bitwarden_sensitive_information_parameter_id"), + table_name="bitwarden_sensitive_information_parameters", + ) + op.drop_table("bitwarden_sensitive_information_parameters") + # ### end Alembic commands ### diff --git a/skyvern/forge/sdk/db/client.py b/skyvern/forge/sdk/db/client.py index 693b1fad..6b1686c4 100644 --- a/skyvern/forge/sdk/db/client.py +++ b/skyvern/forge/sdk/db/client.py @@ -14,6 +14,7 @@ from skyvern.forge.sdk.db.models import ( ArtifactModel, AWSSecretParameterModel, BitwardenLoginCredentialParameterModel, + BitwardenSensitiveInformationParameterModel, OrganizationAuthTokenModel, OrganizationModel, OutputParameterModel, @@ -31,6 +32,7 @@ from skyvern.forge.sdk.db.utils import ( convert_to_artifact, convert_to_aws_secret_parameter, convert_to_bitwarden_login_credential_parameter, + convert_to_bitwarden_sensitive_information_parameter, convert_to_organization, convert_to_organization_auth_token, convert_to_output_parameter, @@ -48,6 +50,7 @@ from skyvern.forge.sdk.schemas.tasks import ProxyLocation, Task, TaskStatus from skyvern.forge.sdk.workflow.models.parameter import ( AWSSecretParameter, BitwardenLoginCredentialParameter, + BitwardenSensitiveInformationParameter, OutputParameter, WorkflowParameter, WorkflowParameterType, @@ -1138,6 +1141,35 @@ class AgentDB: await session.refresh(bitwarden_login_credential_parameter) return convert_to_bitwarden_login_credential_parameter(bitwarden_login_credential_parameter) + async def create_bitwarden_sensitive_information_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_identity_key: str, + bitwarden_identity_fields: list[str], + key: str, + description: str | None = None, + ) -> BitwardenSensitiveInformationParameter: + async with self.Session() as session: + bitwarden_sensitive_information_parameter = BitwardenSensitiveInformationParameterModel( + 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_identity_key=bitwarden_identity_key, + bitwarden_identity_fields=bitwarden_identity_fields, + key=key, + description=description, + ) + session.add(bitwarden_sensitive_information_parameter) + await session.commit() + await session.refresh(bitwarden_sensitive_information_parameter) + return convert_to_bitwarden_sensitive_information_parameter(bitwarden_sensitive_information_parameter) + async def create_output_parameter( self, workflow_id: str, diff --git a/skyvern/forge/sdk/db/id.py b/skyvern/forge/sdk/db/id.py index 540bf257..d31ad69c 100644 --- a/skyvern/forge/sdk/db/id.py +++ b/skyvern/forge/sdk/db/id.py @@ -40,6 +40,7 @@ WORKFLOW_PARAMETER_PREFIX = "wp" AWS_SECRET_PARAMETER_PREFIX = "asp" OUTPUT_PARAMETER_PREFIX = "op" BITWARDEN_LOGIN_CREDENTIAL_PARAMETER_PREFIX = "blc" +BITWARDEN_SENSITIVE_INFORMATION_PARAMETER_PREFIX = "bsi" TASK_GENERATION_PREFIX = "tg" @@ -78,6 +79,11 @@ def generate_bitwarden_login_credential_parameter_id() -> str: return f"{BITWARDEN_LOGIN_CREDENTIAL_PARAMETER_PREFIX}_{int_id}" +def generate_bitwarden_sensitive_information_parameter_id() -> str: + int_id = generate_id() + return f"{BITWARDEN_SENSITIVE_INFORMATION_PARAMETER_PREFIX}_{int_id}" + + def generate_organization_auth_token_id() -> str: int_id = generate_id() return f"{ORGANIZATION_AUTH_TOKEN_PREFIX}_{int_id}" diff --git a/skyvern/forge/sdk/db/models.py b/skyvern/forge/sdk/db/models.py index da593c04..e311db92 100644 --- a/skyvern/forge/sdk/db/models.py +++ b/skyvern/forge/sdk/db/models.py @@ -22,6 +22,7 @@ from skyvern.forge.sdk.db.id import ( generate_artifact_id, generate_aws_secret_parameter_id, generate_bitwarden_login_credential_parameter_id, + generate_bitwarden_sensitive_information_parameter_id, generate_org_id, generate_organization_auth_token_id, generate_output_parameter_id, @@ -293,6 +294,35 @@ class BitwardenLoginCredentialParameterModel(Base): deleted_at = Column(DateTime, nullable=True) +class BitwardenSensitiveInformationParameterModel(Base): + __tablename__ = "bitwarden_sensitive_information_parameters" + + bitwarden_sensitive_information_parameter_id = Column( + String, + primary_key=True, + index=True, + default=generate_bitwarden_sensitive_information_parameter_id, + ) + workflow_id = Column(String, ForeignKey("workflows.workflow_id"), index=True, nullable=False) + key = Column(String, nullable=False) + description = Column(String, nullable=True) + bitwarden_client_id_aws_secret_key = Column(String, nullable=False) + bitwarden_client_secret_aws_secret_key = Column(String, nullable=False) + bitwarden_master_password_aws_secret_key = Column(String, nullable=False) + bitwarden_collection_id = Column(String, nullable=False) + bitwarden_identity_key = Column(String, nullable=False) + # This is a list of fields to extract from the Bitwarden Identity. + bitwarden_identity_fields = Column(JSON, nullable=False) + created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False) + modified_at = Column( + DateTime, + default=datetime.datetime.utcnow, + onupdate=datetime.datetime.utcnow, + nullable=False, + ) + deleted_at = Column(DateTime, nullable=True) + + class WorkflowRunParameterModel(Base): __tablename__ = "workflow_run_parameters" diff --git a/skyvern/forge/sdk/db/utils.py b/skyvern/forge/sdk/db/utils.py index 1f2167f8..e4be48da 100644 --- a/skyvern/forge/sdk/db/utils.py +++ b/skyvern/forge/sdk/db/utils.py @@ -10,6 +10,7 @@ from skyvern.forge.sdk.db.models import ( ArtifactModel, AWSSecretParameterModel, BitwardenLoginCredentialParameterModel, + BitwardenSensitiveInformationParameterModel, OrganizationAuthTokenModel, OrganizationModel, OutputParameterModel, @@ -26,6 +27,7 @@ from skyvern.forge.sdk.schemas.tasks import ProxyLocation, Task, TaskStatus from skyvern.forge.sdk.workflow.models.parameter import ( AWSSecretParameter, BitwardenLoginCredentialParameter, + BitwardenSensitiveInformationParameter, OutputParameter, WorkflowParameter, WorkflowParameterType, @@ -263,6 +265,33 @@ def convert_to_bitwarden_login_credential_parameter( ) +def convert_to_bitwarden_sensitive_information_parameter( + bitwarden_sensitive_information_parameter_model: BitwardenSensitiveInformationParameterModel, + debug_enabled: bool = False, +) -> BitwardenSensitiveInformationParameter: + if debug_enabled: + LOG.debug( + "Converting BitwardenSensitiveInformationParameterModel to BitwardenSensitiveInformationParameter", + bitwarden_sensitive_information_parameter_id=bitwarden_sensitive_information_parameter_model.bitwarden_sensitive_information_parameter_id, + ) + + return BitwardenSensitiveInformationParameter( + bitwarden_sensitive_information_parameter_id=bitwarden_sensitive_information_parameter_model.bitwarden_sensitive_information_parameter_id, + workflow_id=bitwarden_sensitive_information_parameter_model.workflow_id, + key=bitwarden_sensitive_information_parameter_model.key, + description=bitwarden_sensitive_information_parameter_model.description, + bitwarden_client_id_aws_secret_key=bitwarden_sensitive_information_parameter_model.bitwarden_client_id_aws_secret_key, + bitwarden_client_secret_aws_secret_key=bitwarden_sensitive_information_parameter_model.bitwarden_client_secret_aws_secret_key, + bitwarden_master_password_aws_secret_key=bitwarden_sensitive_information_parameter_model.bitwarden_master_password_aws_secret_key, + bitwarden_collection_id=bitwarden_sensitive_information_parameter_model.bitwarden_collection_id, + bitwarden_identity_key=bitwarden_sensitive_information_parameter_model.bitwarden_identity_key, + bitwarden_identity_fields=bitwarden_sensitive_information_parameter_model.bitwarden_identity_fields, + created_at=bitwarden_sensitive_information_parameter_model.created_at, + modified_at=bitwarden_sensitive_information_parameter_model.modified_at, + deleted_at=bitwarden_sensitive_information_parameter_model.deleted_at, + ) + + def convert_to_output_parameter( output_parameter_model: OutputParameterModel, debug_enabled: bool = False ) -> OutputParameter: diff --git a/skyvern/forge/sdk/services/bitwarden.py b/skyvern/forge/sdk/services/bitwarden.py index e93a74ab..5295375b 100644 --- a/skyvern/forge/sdk/services/bitwarden.py +++ b/skyvern/forge/sdk/services/bitwarden.py @@ -29,6 +29,7 @@ class BitwardenConstants(StrEnum): MASTER_PASSWORD = "BW_MASTER_PASSWORD" URL = "BW_URL" BW_COLLECTION_ID = "BW_COLLECTION_ID" + IDENTITY_KEY = "BW_IDENTITY_KEY" USERNAME = "BW_USERNAME" PASSWORD = "BW_PASSWORD" @@ -75,49 +76,10 @@ class BitwardenService: """ Get the secret value from the Bitwarden CLI. """ - # Step 1: Set up environment variables and log in try: - env = { - "BW_CLIENTID": client_id, - "BW_CLIENTSECRET": client_secret, - "BW_PASSWORD": master_password, - } - login_command = ["bw", "login", "--apikey"] - login_result = BitwardenService.run_command(login_command, env) + BitwardenService.login(client_id, client_secret) + session_key = BitwardenService.unlock(master_password) - # Validate the login result - if login_result.stdout and "You are logged in!" not in login_result.stdout: - raise BitwardenLoginError( - f"Failed to log in. stdout: {login_result.stdout} stderr: {login_result.stderr}" - ) - - if login_result.stderr and "You are already logged in as" not in login_result.stderr: - raise BitwardenLoginError( - f"Failed to log in. stdout: {login_result.stdout} stderr: {login_result.stderr}" - ) - - LOG.info("Bitwarden login successful") - - # Step 2: Unlock the vault - unlock_command = ["bw", "unlock", "--passwordenv", "BW_PASSWORD"] - unlock_result = BitwardenService.run_command(unlock_command, env) - - # Validate the unlock result - if unlock_result.stdout and "Your vault is now unlocked!" not in unlock_result.stdout: - raise BitwardenUnlockError( - f"Failed to unlock vault. stdout: {unlock_result.stdout} stderr: {unlock_result.stderr}" - ) - - # Extract session key - try: - session_key = BitwardenService._extract_session_key(unlock_result.stdout) - except Exception as e: - raise BitwardenUnlockError(f"Unable to extract session key: {str(e)}") - - if not session_key: - raise BitwardenUnlockError("Session key is empty.") - - # Step 3: Retrieve the items # Extract the domain from the URL and search for items in Bitwarden with that domain domain = tldextract.extract(url).domain list_command = [ @@ -187,6 +149,123 @@ class BitwardenService: # Step 4: Log out BitwardenService.logout() + @staticmethod + def get_sensitive_information_from_identity( + client_id: str, + client_secret: str, + master_password: str, + collection_id: str, + identity_key: str, + identity_fields: list[str], + ) -> dict[str, str]: + """ + Get the sensitive information from the Bitwarden CLI. + """ + try: + BitwardenService.login(client_id, client_secret) + session_key = BitwardenService.unlock(master_password) + + # Step 3: Retrieve the items + list_command = [ + "bw", + "list", + "items", + "--search", + identity_key, + "--session", + session_key, + "--collectionid", + collection_id, + ] + items_result = BitwardenService.run_command(list_command) + + # Parse the items and extract sensitive information + try: + items = json.loads(items_result.stdout) + except json.JSONDecodeError: + raise BitwardenListItemsError("Failed to parse items JSON. Output: " + items_result.stdout) + + if not items: + raise BitwardenListItemsError( + f"No items found in Bitwarden for identity key: {identity_key} in collection with ID: {collection_id}" + ) + + # Filter the identity items + # https://bitwarden.com/help/cli/#create lists the type of the identity items as 4 + identity_items = [item for item in items if item["type"] == 4] + + if len(identity_items) != 1: + raise BitwardenListItemsError( + f"Expected exactly one identity item, but found {len(identity_items)} items for identity key: {identity_key} in collection with ID: {collection_id}" + ) + + identity_item = identity_items[0] + + sensitive_information: dict[str, str] = {} + for field in identity_fields: + # The identity item may store sensitive information in custom fields or default fields + # Custom fields are prioritized over default fields + # TODO (kerem): Make this case insensitive? + if field in identity_item["fields"]: + sensitive_information[field] = identity_item["fields"][field]["value"] + elif field in identity_item["identity"]: + sensitive_information[field] = identity_item["identity"][field] + + return sensitive_information + + finally: + # Step 4: Log out + BitwardenService.logout() + + @staticmethod + def login(client_id: str, client_secret: str) -> None: + """ + Log in to the Bitwarden CLI. + """ + env = { + "BW_CLIENTID": client_id, + "BW_CLIENTSECRET": client_secret, + } + login_command = ["bw", "login", "--apikey"] + login_result = BitwardenService.run_command(login_command, env) + + # Validate the login result + if login_result.stdout and "You are logged in!" not in login_result.stdout: + raise BitwardenLoginError(f"Failed to log in. stdout: {login_result.stdout} stderr: {login_result.stderr}") + + if login_result.stderr and "You are already logged in as" not in login_result.stderr: + raise BitwardenLoginError(f"Failed to log in. stdout: {login_result.stdout} stderr: {login_result.stderr}") + + LOG.info("Bitwarden login successful") + + @staticmethod + def unlock(master_password: str) -> str: + """ + Unlock the Bitwarden CLI. + """ + env = { + "BW_PASSWORD": master_password, + } + unlock_command = ["bw", "unlock", "--passwordenv", "BW_PASSWORD"] + unlock_result = BitwardenService.run_command(unlock_command, env) + + # Validate the unlock result + if unlock_result.stdout and "Your vault is now unlocked!" not in unlock_result.stdout: + raise BitwardenUnlockError( + f"Failed to unlock vault. stdout: {unlock_result.stdout} stderr: {unlock_result.stderr}" + ) + + # Extract session key + try: + session_key = BitwardenService._extract_session_key(unlock_result.stdout) + except Exception as e: + raise BitwardenUnlockError(f"Unable to extract session key: {str(e)}") + + if not session_key: + raise BitwardenUnlockError("Session key is empty.") + + return session_key + @staticmethod def logout() -> None: """ diff --git a/skyvern/forge/sdk/workflow/context_manager.py b/skyvern/forge/sdk/workflow/context_manager.py index 64dec72f..455641cc 100644 --- a/skyvern/forge/sdk/workflow/context_manager.py +++ b/skyvern/forge/sdk/workflow/context_manager.py @@ -194,6 +194,55 @@ 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 + 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 + + bitwarden_identity_key = parameter.bitwarden_identity_key + if self.has_parameter(parameter.bitwarden_identity_key) and self.has_value( + parameter.bitwarden_identity_key + ): + bitwarden_identity_key = self.values[parameter.bitwarden_identity_key] + + collection_id = parameter.bitwarden_collection_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] + + try: + sensitive_values = BitwardenService.get_sensitive_information_from_identity( + client_id, + client_secret, + master_password, + collection_id, + bitwarden_identity_key, + parameter.bitwarden_identity_fields, + ) + if sensitive_values: + self.secrets[BitwardenConstants.IDENTITY_KEY] = bitwarden_identity_key + self.secrets[BitwardenConstants.CLIENT_SECRET] = client_secret + self.secrets[BitwardenConstants.CLIENT_ID] = client_id + self.secrets[BitwardenConstants.MASTER_PASSWORD] = master_password + self.secrets[BitwardenConstants.BW_COLLECTION_ID] = collection_id + + self.values[parameter.key] = {} + for key, value in sensitive_values.items(): + random_secret_id = self.generate_random_secret_id() + secret_id = f"{random_secret_id}_{key}" + self.secrets[secret_id] = value + self.values[parameter.key][key] = secret_id + + except BitwardenBaseError as e: + LOG.error(f"Failed to get sensitive information from Bitwarden. Error: {e}") + raise e elif isinstance(parameter, ContextParameter): if isinstance(parameter.source, WorkflowParameter): # TODO (kerem): set this while initializing the context manager diff --git a/skyvern/forge/sdk/workflow/models/parameter.py b/skyvern/forge/sdk/workflow/models/parameter.py index 5bd75da7..4740d247 100644 --- a/skyvern/forge/sdk/workflow/models/parameter.py +++ b/skyvern/forge/sdk/workflow/models/parameter.py @@ -12,6 +12,7 @@ class ParameterType(StrEnum): CONTEXT = "context" AWS_SECRET = "aws_secret" BITWARDEN_LOGIN_CREDENTIAL = "bitwarden_login_credential" + BITWARDEN_SENSITIVE_INFORMATION = "bitwarden_sensitive_information" OUTPUT = "output" @@ -61,6 +62,30 @@ class BitwardenLoginCredentialParameter(Parameter): deleted_at: datetime | None = None +class BitwardenSensitiveInformationParameter(Parameter): + parameter_type: Literal[ParameterType.BITWARDEN_SENSITIVE_INFORMATION] = ( + ParameterType.BITWARDEN_SENSITIVE_INFORMATION + ) + # parameter fields + bitwarden_sensitive_information_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 collection id to filter the Bitwarden Identity from + bitwarden_collection_id: str + # unique key to identify the Bitwarden Identity in the collection + # this has to be in the identity's name + bitwarden_identity_key: str + # fields to extract from the Bitwarden Identity. Custom fields are prioritized over default identity fields + bitwarden_identity_fields: list[str] + + created_at: datetime + modified_at: datetime + deleted_at: datetime | None = None + + class WorkflowParameterType(StrEnum): STRING = "string" INTEGER = "integer" @@ -124,6 +149,7 @@ ParameterSubclasses = Union[ ContextParameter, AWSSecretParameter, BitwardenLoginCredentialParameter, + BitwardenSensitiveInformationParameter, OutputParameter, ] PARAMETER_TYPE = Annotated[ParameterSubclasses, Field(discriminator="parameter_type")] diff --git a/skyvern/forge/sdk/workflow/models/yaml.py b/skyvern/forge/sdk/workflow/models/yaml.py index c5c83ef2..88b271b5 100644 --- a/skyvern/forge/sdk/workflow/models/yaml.py +++ b/skyvern/forge/sdk/workflow/models/yaml.py @@ -41,6 +41,28 @@ class BitwardenLoginCredentialParameterYAML(ParameterYAML): bitwarden_collection_id: str | None = None +class BitwardenSensitiveInformationParameterYAML(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_SENSITIVE_INFORMATION] = ( + ParameterType.BITWARDEN_SENSITIVE_INFORMATION + ) # 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 collection id to filter the Bitwarden Identity from + bitwarden_collection_id: str + # unique key to identify the Bitwarden Identity in the collection + # this has to be in the identity's name + bitwarden_identity_key: str + # fields to extract from the Bitwarden Identity. Custom fields are prioritized over default identity fields + bitwarden_identity_fields: list[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" @@ -172,6 +194,7 @@ class FileParserBlockYAML(BlockYAML): PARAMETER_YAML_SUBCLASSES = ( AWSSecretParameterYAML | BitwardenLoginCredentialParameterYAML + | BitwardenSensitiveInformationParameterYAML | WorkflowParameterYAML | ContextParameterYAML | OutputParameterYAML diff --git a/skyvern/forge/sdk/workflow/service.py b/skyvern/forge/sdk/workflow/service.py index 90a91590..fc52277a 100644 --- a/skyvern/forge/sdk/workflow/service.py +++ b/skyvern/forge/sdk/workflow/service.py @@ -489,6 +489,30 @@ class WorkflowService: bitwarden_collection_id=bitwarden_collection_id, ) + async def create_bitwarden_sensitive_information_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_identity_key: str, + bitwarden_identity_fields: list[str], + key: str, + description: str | None = None, + ) -> Parameter: + return await app.DATABASE.create_bitwarden_sensitive_information_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_identity_key=bitwarden_identity_key, + bitwarden_identity_fields=bitwarden_identity_fields, + key=key, + description=description, + ) + async def create_output_parameter( self, workflow_id: str, key: str, description: str | None = None ) -> OutputParameter: @@ -865,6 +889,18 @@ class WorkflowService: description=parameter.description, bitwarden_collection_id=parameter.bitwarden_collection_id, ) + elif parameter.parameter_type == ParameterType.BITWARDEN_SENSITIVE_INFORMATION: + parameters[parameter.key] = await self.create_bitwarden_sensitive_information_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_identity_key=parameter.bitwarden_identity_key, + bitwarden_identity_fields=parameter.bitwarden_identity_fields, + 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,