mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2025-09-14 17:29:40 +00:00
Add bitwarden_item_id to bitwarden_login_credential (#1871)
Co-authored-by: Muhammed Salih Altun <muhammedsalihaltun@gmail.com>
This commit is contained in:
parent
c7e6a5c84b
commit
8a1b0f3797
9 changed files with 107 additions and 14 deletions
|
@ -0,0 +1,37 @@
|
|||
"""add bitwarden_item_id and make url_parameter_key optional in bitwarden_login_credential
|
||||
|
||||
Revision ID: 268dcc995513
|
||||
Revises: a21b9f4f51d2
|
||||
Create Date: 2025-03-03 16:42:11.868247+00:00
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "268dcc995513"
|
||||
down_revision: Union[str, None] = "a21b9f4f51d2"
|
||||
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.add_column("bitwarden_login_credential_parameters", sa.Column("bitwarden_item_id", sa.String(), nullable=True))
|
||||
op.alter_column(
|
||||
"bitwarden_login_credential_parameters", "url_parameter_key", existing_type=sa.VARCHAR(), nullable=True
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column(
|
||||
"bitwarden_login_credential_parameters", "url_parameter_key", existing_type=sa.VARCHAR(), nullable=False
|
||||
)
|
||||
op.drop_column("bitwarden_login_credential_parameters", "bitwarden_item_id")
|
||||
# ### end Alembic commands ###
|
|
@ -1586,10 +1586,11 @@ class AgentDB:
|
|||
bitwarden_client_id_aws_secret_key: str,
|
||||
bitwarden_client_secret_aws_secret_key: str,
|
||||
bitwarden_master_password_aws_secret_key: str,
|
||||
url_parameter_key: str,
|
||||
key: str,
|
||||
url_parameter_key: str | None = None,
|
||||
description: str | None = None,
|
||||
bitwarden_collection_id: str | None = None,
|
||||
bitwarden_item_id: str | None = None,
|
||||
) -> BitwardenLoginCredentialParameter:
|
||||
async with self.Session() as session:
|
||||
bitwarden_login_credential_parameter = BitwardenLoginCredentialParameterModel(
|
||||
|
@ -1601,6 +1602,7 @@ class AgentDB:
|
|||
key=key,
|
||||
description=description,
|
||||
bitwarden_collection_id=bitwarden_collection_id,
|
||||
bitwarden_item_id=bitwarden_item_id,
|
||||
)
|
||||
session.add(bitwarden_login_credential_parameter)
|
||||
await session.commit()
|
||||
|
|
|
@ -326,7 +326,8 @@ class BitwardenLoginCredentialParameterModel(Base):
|
|||
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=True, default=None)
|
||||
url_parameter_key = Column(String, nullable=False)
|
||||
bitwarden_item_id = Column(String, nullable=True, default=None)
|
||||
url_parameter_key = Column(String, nullable=True, default=None)
|
||||
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
||||
modified_at = Column(
|
||||
DateTime,
|
||||
|
|
|
@ -286,6 +286,7 @@ def convert_to_bitwarden_login_credential_parameter(
|
|||
bitwarden_client_secret_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_client_secret_aws_secret_key,
|
||||
bitwarden_master_password_aws_secret_key=bitwarden_login_credential_parameter_model.bitwarden_master_password_aws_secret_key,
|
||||
bitwarden_collection_id=bitwarden_login_credential_parameter_model.bitwarden_collection_id,
|
||||
bitwarden_item_id=bitwarden_login_credential_parameter_model.bitwarden_item_id,
|
||||
url_parameter_key=bitwarden_login_credential_parameter_model.url_parameter_key,
|
||||
created_at=bitwarden_login_credential_parameter_model.created_at,
|
||||
modified_at=bitwarden_login_credential_parameter_model.modified_at,
|
||||
|
|
|
@ -106,7 +106,7 @@ class BitwardenConstants(StrEnum):
|
|||
URL = "BW_URL"
|
||||
BW_COLLECTION_ID = "BW_COLLECTION_ID"
|
||||
IDENTITY_KEY = "BW_IDENTITY_KEY"
|
||||
ITEM_ID = "BW_ITEM_ID"
|
||||
BW_ITEM_ID = "BW_ITEM_ID"
|
||||
|
||||
USERNAME = "BW_USERNAME"
|
||||
PASSWORD = "BW_PASSWORD"
|
||||
|
@ -190,8 +190,9 @@ class BitwardenService:
|
|||
master_password: str,
|
||||
bw_organization_id: str | None,
|
||||
bw_collection_ids: list[str] | None,
|
||||
url: str,
|
||||
url: str | None = None,
|
||||
collection_id: str | None = None,
|
||||
item_id: str | None = None,
|
||||
max_retries: int = settings.BITWARDEN_MAX_RETRIES,
|
||||
timeout: int = settings.BITWARDEN_TIMEOUT_SECONDS,
|
||||
) -> dict[str, str]:
|
||||
|
@ -215,6 +216,7 @@ class BitwardenService:
|
|||
bw_collection_ids=bw_collection_ids,
|
||||
url=url,
|
||||
collection_id=collection_id,
|
||||
item_id=item_id,
|
||||
timeout=timeout,
|
||||
)
|
||||
except BitwardenAccessDeniedError as e:
|
||||
|
@ -271,8 +273,9 @@ class BitwardenService:
|
|||
master_password: str,
|
||||
bw_organization_id: str | None,
|
||||
bw_collection_ids: list[str] | None,
|
||||
url: str,
|
||||
url: str | None = None,
|
||||
collection_id: str | None = None,
|
||||
item_id: str | None = None,
|
||||
timeout: int = 60,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
|
@ -283,6 +286,26 @@ class BitwardenService:
|
|||
await BitwardenService.sync()
|
||||
session_key = await BitwardenService.unlock(master_password)
|
||||
|
||||
if item_id: # if item_id provided, get single item by item id
|
||||
command = ["bw", "get", "item", item_id, "--session", session_key]
|
||||
item_result = await BitwardenService.run_command(command)
|
||||
if item_result.stderr:
|
||||
raise BitwardenGetItemError(
|
||||
f"Failed to get the bitwarden item {item_id}. Error: {item_result.stderr}"
|
||||
)
|
||||
try:
|
||||
item = json.loads(item_result.stdout)
|
||||
except json.JSONDecodeError:
|
||||
raise BitwardenGetItemError(f"Failed to parse item JSON for item ID: {item_id}")
|
||||
return {
|
||||
BitwardenConstants.USERNAME: item["login"]["username"],
|
||||
BitwardenConstants.PASSWORD: item["login"]["password"],
|
||||
BitwardenConstants.TOTP: item["login"]["totp"],
|
||||
}
|
||||
elif not url:
|
||||
# if item_id is not provided, we need a url to search for items
|
||||
raise BitwardenGetItemError("No url or item ID provided")
|
||||
|
||||
# Extract the domain from the URL and search for items in Bitwarden with that domain
|
||||
extract_url = tldextract.extract(url)
|
||||
domain = extract_url.domain
|
||||
|
|
|
@ -165,6 +165,7 @@ class WorkflowRunContext:
|
|||
bw_organization_id=self.secrets[BitwardenConstants.BW_ORGANIZATION_ID],
|
||||
bw_collection_ids=self.secrets[BitwardenConstants.BW_COLLECTION_IDS],
|
||||
collection_id=self.secrets[BitwardenConstants.BW_COLLECTION_ID],
|
||||
item_id=self.secrets[BitwardenConstants.BW_ITEM_ID],
|
||||
)
|
||||
return secret_credentials
|
||||
|
||||
|
@ -241,11 +242,17 @@ class WorkflowRunContext:
|
|||
LOG.error(f"Failed to get Bitwarden login credentials from AWS secrets. Error: {e}")
|
||||
raise e
|
||||
|
||||
if self.has_parameter(parameter.url_parameter_key) and self.has_value(parameter.url_parameter_key):
|
||||
if (
|
||||
parameter.url_parameter_key
|
||||
and self.has_parameter(parameter.url_parameter_key)
|
||||
and self.has_value(parameter.url_parameter_key)
|
||||
):
|
||||
url = self.values[parameter.url_parameter_key]
|
||||
elif parameter.url_parameter_key:
|
||||
# If a key can't be found within the parameter values dict, assume it's a URL (and not a URL Parameter)
|
||||
url = parameter.url_parameter_key
|
||||
elif parameter.bitwarden_item_id:
|
||||
url = None
|
||||
else:
|
||||
LOG.error(f"URL parameter {parameter.url_parameter_key} not found or has no value")
|
||||
raise SkyvernException("URL parameter for Bitwarden login credentials not found or has no value")
|
||||
|
@ -259,6 +266,13 @@ class WorkflowRunContext:
|
|||
else:
|
||||
collection_id = parameter.bitwarden_collection_id
|
||||
|
||||
item_id = None
|
||||
if parameter.bitwarden_item_id:
|
||||
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
|
||||
|
||||
try:
|
||||
secret_credentials = await BitwardenService.get_secret_value_from_url(
|
||||
client_id,
|
||||
|
@ -268,6 +282,7 @@ class WorkflowRunContext:
|
|||
organization.bw_collection_ids,
|
||||
url,
|
||||
collection_id=collection_id,
|
||||
item_id=item_id,
|
||||
)
|
||||
if secret_credentials:
|
||||
self.secrets[BitwardenConstants.BW_ORGANIZATION_ID] = organization.bw_organization_id
|
||||
|
@ -277,6 +292,7 @@ class WorkflowRunContext:
|
|||
self.secrets[BitwardenConstants.CLIENT_ID] = client_id
|
||||
self.secrets[BitwardenConstants.MASTER_PASSWORD] = master_password
|
||||
self.secrets[BitwardenConstants.BW_COLLECTION_ID] = parameter.bitwarden_collection_id
|
||||
self.secrets[BitwardenConstants.BW_ITEM_ID] = item_id
|
||||
|
||||
random_secret_id = self.generate_random_secret_id()
|
||||
# username secret
|
||||
|
@ -410,7 +426,7 @@ class WorkflowRunContext:
|
|||
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
|
||||
self.secrets[BitwardenConstants.BW_ITEM_ID] = item_id
|
||||
|
||||
fields_to_obfuscate = {
|
||||
BitwardenConstants.CREDIT_CARD_NUMBER: "card_number",
|
||||
|
|
|
@ -56,10 +56,12 @@ class BitwardenLoginCredentialParameter(Parameter):
|
|||
bitwarden_client_secret_aws_secret_key: str
|
||||
bitwarden_master_password_aws_secret_key: str
|
||||
# url to request the login credentials from bitwarden
|
||||
url_parameter_key: str
|
||||
url_parameter_key: str | None = None
|
||||
# bitwarden collection id to filter the login credentials from,
|
||||
# if not provided, no filtering will be done
|
||||
bitwarden_collection_id: str | None = None
|
||||
# bitwarden item id to request the login credential
|
||||
bitwarden_item_id: str | None = None
|
||||
|
||||
created_at: datetime
|
||||
modified_at: datetime
|
||||
|
|
|
@ -37,10 +37,12 @@ class BitwardenLoginCredentialParameterYAML(ParameterYAML):
|
|||
bitwarden_client_secret_aws_secret_key: str
|
||||
bitwarden_master_password_aws_secret_key: str
|
||||
# parameter key for the url to request the login credentials from bitwarden
|
||||
url_parameter_key: str
|
||||
url_parameter_key: str | None = None
|
||||
# bitwarden collection id to filter the login credentials from,
|
||||
# if not provided, no filtering will be done
|
||||
bitwarden_collection_id: str | None = None
|
||||
# bitwarden item id to request the login credential
|
||||
bitwarden_item_id: str | None = None
|
||||
|
||||
|
||||
class CredentialParameterYAML(ParameterYAML):
|
||||
|
|
|
@ -804,20 +804,22 @@ class WorkflowService:
|
|||
bitwarden_client_id_aws_secret_key: str,
|
||||
bitwarden_client_secret_aws_secret_key: str,
|
||||
bitwarden_master_password_aws_secret_key: str,
|
||||
url_parameter_key: str,
|
||||
key: str,
|
||||
url_parameter_key: str | None = None,
|
||||
description: str | None = None,
|
||||
bitwarden_collection_id: str | None = None,
|
||||
bitwarden_item_id: str | None = None,
|
||||
) -> Parameter:
|
||||
return await app.DATABASE.create_bitwarden_login_credential_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,
|
||||
url_parameter_key=url_parameter_key,
|
||||
key=key,
|
||||
url_parameter_key=url_parameter_key,
|
||||
description=description,
|
||||
bitwarden_collection_id=bitwarden_collection_id,
|
||||
bitwarden_item_id=bitwarden_item_id,
|
||||
)
|
||||
|
||||
async def create_credential_parameter(
|
||||
|
@ -1397,11 +1399,17 @@ class WorkflowService:
|
|||
credential_id=parameter.credential_id,
|
||||
)
|
||||
elif parameter.parameter_type == ParameterType.BITWARDEN_LOGIN_CREDENTIAL:
|
||||
if not parameter.bitwarden_collection_id:
|
||||
if not parameter.bitwarden_collection_id and not parameter.bitwarden_item_id:
|
||||
raise WorkflowParameterMissingRequiredValue(
|
||||
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
|
||||
workflow_parameter_key=parameter.key,
|
||||
required_value="bitwarden_collection_id",
|
||||
required_value="bitwarden_collection_id or bitwarden_item_id",
|
||||
)
|
||||
if parameter.bitwarden_collection_id and not parameter.url_parameter_key:
|
||||
raise WorkflowParameterMissingRequiredValue(
|
||||
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
|
||||
workflow_parameter_key=parameter.key,
|
||||
required_value="url_parameter_key",
|
||||
)
|
||||
parameters[parameter.key] = await self.create_bitwarden_login_credential_parameter(
|
||||
workflow_id=workflow.workflow_id,
|
||||
|
@ -1412,6 +1420,7 @@ class WorkflowService:
|
|||
key=parameter.key,
|
||||
description=parameter.description,
|
||||
bitwarden_collection_id=parameter.bitwarden_collection_id,
|
||||
bitwarden_item_id=parameter.bitwarden_item_id,
|
||||
)
|
||||
elif parameter.parameter_type == ParameterType.BITWARDEN_SENSITIVE_INFORMATION:
|
||||
parameters[parameter.key] = await self.create_bitwarden_sensitive_information_parameter(
|
||||
|
@ -1434,7 +1443,7 @@ class WorkflowService:
|
|||
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
|
||||
# TODO: remove "# type: ignore" after ensuring bitwarden_collection_id is always set
|
||||
bitwarden_collection_id=parameter.bitwarden_collection_id, # type: ignore
|
||||
bitwarden_item_id=parameter.bitwarden_item_id,
|
||||
bitwarden_item_id=parameter.bitwarden_item_id, # type: ignore
|
||||
key=parameter.key,
|
||||
description=parameter.description,
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue