[Backend] Add SECRET credential type for storing generic sensitive values (#4246)
This commit is contained in:
@@ -4573,6 +4573,7 @@ class AgentDB:
|
||||
card_last4: str | None,
|
||||
card_brand: str | None,
|
||||
totp_identifier: str | None = None,
|
||||
secret_label: str | None = None,
|
||||
) -> Credential:
|
||||
async with self.Session() as session:
|
||||
credential = CredentialModel(
|
||||
@@ -4586,6 +4587,7 @@ class AgentDB:
|
||||
totp_identifier=totp_identifier,
|
||||
card_last4=card_last4,
|
||||
card_brand=card_brand,
|
||||
secret_label=secret_label,
|
||||
)
|
||||
session.add(credential)
|
||||
await session.commit()
|
||||
|
||||
@@ -901,6 +901,7 @@ class CredentialModel(Base):
|
||||
totp_identifier = Column(String, nullable=True, default=None)
|
||||
card_last4 = Column(String, nullable=True)
|
||||
card_brand = Column(String, nullable=True)
|
||||
secret_label = Column(String, nullable=True)
|
||||
|
||||
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
||||
modified_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False)
|
||||
|
||||
@@ -29,6 +29,7 @@ from skyvern.forge.sdk.schemas.credentials import (
|
||||
CredentialVaultType,
|
||||
CreditCardCredentialResponse,
|
||||
PasswordCredentialResponse,
|
||||
SecretCredentialResponse,
|
||||
)
|
||||
from skyvern.forge.sdk.schemas.organizations import (
|
||||
AzureClientSecretCredentialResponse,
|
||||
@@ -267,6 +268,14 @@ async def create_credential(
|
||||
credential_type=data.credential_type,
|
||||
name=data.name,
|
||||
)
|
||||
elif data.credential_type == CredentialType.SECRET:
|
||||
credential_response = SecretCredentialResponse(secret_label=data.credential.secret_label)
|
||||
return CredentialResponse(
|
||||
credential=credential_response,
|
||||
credential_id=credential.credential_id,
|
||||
credential_type=data.credential_type,
|
||||
name=data.name,
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported credential type: {data.credential_type}")
|
||||
|
||||
@@ -764,5 +773,13 @@ def _convert_to_response(credential: Credential) -> CredentialResponse:
|
||||
credential_type=credential.credential_type,
|
||||
name=credential.name,
|
||||
)
|
||||
elif credential.credential_type == CredentialType.SECRET:
|
||||
credential_response = SecretCredentialResponse(secret_label=credential.secret_label)
|
||||
return CredentialResponse(
|
||||
credential=credential_response,
|
||||
credential_id=credential.credential_id,
|
||||
credential_type=credential.credential_type,
|
||||
name=credential.name,
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Credential type not supported")
|
||||
|
||||
@@ -15,6 +15,7 @@ class CredentialType(StrEnum):
|
||||
|
||||
PASSWORD = "password"
|
||||
CREDIT_CARD = "credit_card"
|
||||
SECRET = "secret"
|
||||
|
||||
|
||||
class TotpType(StrEnum):
|
||||
@@ -49,6 +50,12 @@ class CreditCardCredentialResponse(BaseModel):
|
||||
brand: str = Field(..., description="Brand of the credit card", examples=["visa"])
|
||||
|
||||
|
||||
class SecretCredentialResponse(BaseModel):
|
||||
"""Response model for secret credentials."""
|
||||
|
||||
secret_label: str | None = Field(default=None, description="Optional label for the stored secret")
|
||||
|
||||
|
||||
class PasswordCredential(BaseModel):
|
||||
"""Base model for password credentials."""
|
||||
|
||||
@@ -115,13 +122,22 @@ class NonEmptyCreditCardCredential(CreditCardCredential):
|
||||
)
|
||||
|
||||
|
||||
class SecretCredential(BaseModel):
|
||||
"""Generic secret credential."""
|
||||
|
||||
secret_value: str = Field(..., min_length=1, description="The secret value", examples=["sk-abc123"])
|
||||
secret_label: str | None = Field(default=None, description="Optional label describing the secret")
|
||||
|
||||
|
||||
class CredentialItem(BaseModel):
|
||||
"""Model representing a credential item in the system."""
|
||||
|
||||
item_id: str = Field(..., description="Unique identifier for the credential item", examples=["cred_1234567890"])
|
||||
name: str = Field(..., description="Name of the credential", examples=["Skyvern Login"])
|
||||
credential_type: CredentialType = Field(..., description="Type of the credential. Eg password, credit card, etc.")
|
||||
credential: PasswordCredential | CreditCardCredential = Field(..., description="The actual credential data")
|
||||
credential: PasswordCredential | CreditCardCredential | SecretCredential = Field(
|
||||
..., description="The actual credential data"
|
||||
)
|
||||
|
||||
|
||||
class CreateCredentialRequest(BaseModel):
|
||||
@@ -129,7 +145,7 @@ class CreateCredentialRequest(BaseModel):
|
||||
|
||||
name: str = Field(..., description="Name of the credential", examples=["Amazon Login"])
|
||||
credential_type: CredentialType = Field(..., description="Type of credential to create")
|
||||
credential: NonEmptyPasswordCredential | NonEmptyCreditCardCredential = Field(
|
||||
credential: NonEmptyPasswordCredential | NonEmptyCreditCardCredential | SecretCredential = Field(
|
||||
...,
|
||||
description="The credential data to store",
|
||||
examples=[{"username": "user@example.com", "password": "securepassword123"}],
|
||||
@@ -140,7 +156,7 @@ class CredentialResponse(BaseModel):
|
||||
"""Response model for credential operations."""
|
||||
|
||||
credential_id: str = Field(..., description="Unique identifier for the credential", examples=["cred_1234567890"])
|
||||
credential: PasswordCredentialResponse | CreditCardCredentialResponse = Field(
|
||||
credential: PasswordCredentialResponse | CreditCardCredentialResponse | SecretCredentialResponse = Field(
|
||||
..., description="The credential data"
|
||||
)
|
||||
credential_type: CredentialType = Field(..., description="Type of the credential")
|
||||
@@ -173,6 +189,7 @@ class Credential(BaseModel):
|
||||
)
|
||||
card_last4: str | None = Field(..., description="For credit_card credentials: the last four digits of the card")
|
||||
card_brand: str | None = Field(..., description="For credit_card credentials: the card brand")
|
||||
secret_label: str | None = Field(default=None, description="For secret credentials: optional label")
|
||||
|
||||
created_at: datetime = Field(..., description="Timestamp when the credential was created")
|
||||
modified_at: datetime = Field(..., description="Timestamp when the credential was last modified")
|
||||
|
||||
@@ -14,6 +14,7 @@ from skyvern.forge.sdk.schemas.credentials import (
|
||||
CredentialVaultType,
|
||||
CreditCardCredential,
|
||||
PasswordCredential,
|
||||
SecretCredential,
|
||||
)
|
||||
from skyvern.forge.sdk.services.credential.credential_vault_service import CredentialVaultService
|
||||
|
||||
@@ -36,8 +37,14 @@ class AzureCredentialVaultService(CredentialVaultService):
|
||||
card_brand: str
|
||||
card_holder_name: str
|
||||
|
||||
class _SecretCredentialDataImage(BaseModel):
|
||||
type: Literal["secret"]
|
||||
secret_value: str
|
||||
secret_label: str | None = None
|
||||
|
||||
_CredentialDataImage = Annotated[
|
||||
Union[_PasswordCredentialDataImage, _CreditCardCredentialDataImage], Field(discriminator="type")
|
||||
Union[_PasswordCredentialDataImage, _CreditCardCredentialDataImage, _SecretCredentialDataImage],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
def __init__(self, client: AsyncAzureVaultClient, vault_name: str):
|
||||
@@ -128,13 +135,20 @@ class AzureCredentialVaultService(CredentialVaultService):
|
||||
name=db_credential.name,
|
||||
credential_type=CredentialType.CREDIT_CARD,
|
||||
)
|
||||
elif isinstance(data, AzureCredentialVaultService._SecretCredentialDataImage):
|
||||
return CredentialItem(
|
||||
item_id=db_credential.item_id,
|
||||
credential=SecretCredential(secret_value=data.secret_value, secret_label=data.secret_label),
|
||||
name=db_credential.name,
|
||||
credential_type=CredentialType.SECRET,
|
||||
)
|
||||
else:
|
||||
raise TypeError(f"Invalid credential type: {type(data)}")
|
||||
|
||||
async def _create_azure_secret_item(
|
||||
self,
|
||||
organization_id: str,
|
||||
credential: PasswordCredential | CreditCardCredential,
|
||||
credential: PasswordCredential | CreditCardCredential | SecretCredential,
|
||||
) -> str:
|
||||
if isinstance(credential, PasswordCredential):
|
||||
data = AzureCredentialVaultService._PasswordCredentialDataImage(
|
||||
@@ -153,6 +167,12 @@ class AzureCredentialVaultService(CredentialVaultService):
|
||||
card_brand=credential.card_brand,
|
||||
card_holder_name=credential.card_holder_name,
|
||||
)
|
||||
elif isinstance(credential, SecretCredential):
|
||||
data = AzureCredentialVaultService._SecretCredentialDataImage(
|
||||
type="secret",
|
||||
secret_value=credential.secret_value,
|
||||
secret_label=credential.secret_label,
|
||||
)
|
||||
else:
|
||||
raise TypeError(f"Invalid credential type: {type(credential)}")
|
||||
|
||||
|
||||
@@ -68,5 +68,19 @@ class CredentialVaultService(ABC):
|
||||
card_brand=data.credential.card_brand,
|
||||
totp_identifier=None,
|
||||
)
|
||||
elif data.credential_type == CredentialType.SECRET:
|
||||
return await app.DATABASE.create_credential(
|
||||
organization_id=organization_id,
|
||||
name=data.name,
|
||||
vault_type=vault_type,
|
||||
item_id=item_id,
|
||||
credential_type=data.credential_type,
|
||||
username=None,
|
||||
totp_type="none",
|
||||
card_last4=None,
|
||||
card_brand=None,
|
||||
totp_identifier=None,
|
||||
secret_label=data.credential.secret_label,
|
||||
)
|
||||
else:
|
||||
raise Exception(f"Unsupported credential type: {data.credential_type}")
|
||||
|
||||
Reference in New Issue
Block a user