security(files): scrub connector credentials on deletion

This commit is contained in:
2026-07-21 16:16:47 +02:00
parent cffe161f29
commit 65d8ed80b5
12 changed files with 824 additions and 55 deletions

View File

@@ -108,11 +108,14 @@ from govoplan_files.backend.storage.connector_credential_store import (
ConnectorCredential,
connector_credential_from_row,
create_connector_credential_row,
deactivate_connector_credential_row,
get_connector_credential_row,
list_database_connector_credentials,
update_connector_credential_row,
)
from govoplan_files.backend.storage.connector_credential_deletion import (
delete_connector_credential_row,
delete_connector_profile_row,
)
from govoplan_files.backend.storage.connector_browse import (
ConnectorBrowseError,
ConnectorBrowseUnsupported,
@@ -127,7 +130,6 @@ from govoplan_files.backend.storage.connector_deployment import (
from govoplan_files.backend.storage.connector_profile_store import (
connector_profile_from_row,
create_connector_profile_row,
deactivate_connector_profile_row,
get_connector_profile_row,
list_database_connector_profiles,
update_connector_profile_row,
@@ -2530,6 +2532,7 @@ def discover_connector_endpoint(
reject_api_controlled_deployment_references(
password_env=payload.credentials.password_env,
token_env=payload.credentials.token_env,
secret_ref=payload.credentials.secret_ref,
metadata=payload.metadata,
)
except ValueError as exc:
@@ -2856,23 +2859,49 @@ def deactivate_connector_credential(
raise _http_error(exc, not_found=True) from exc
_require_connector_credential_write(principal, row.scope_type)
try:
deactivate_connector_credential_row(session, row, user_id=principal.user.id)
_record_connector_settings_change(
deletion = delete_connector_credential_row(
session,
collection=FILES_CONNECTOR_CREDENTIALS_COLLECTION,
resource_type=FILES_CONNECTOR_CREDENTIAL_RESOURCE,
resource_id=row.id,
operation="deleted",
principal=principal,
tenant_id=row.tenant_id,
payload={"scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider},
row,
deletion_reason="api_delete",
user_id=principal.user.id,
api_key_id=principal.api_key.id if principal.api_key else None,
)
if deletion.changed:
_record_connector_settings_change(
session,
collection=FILES_CONNECTOR_CREDENTIALS_COLLECTION,
resource_type=FILES_CONNECTOR_CREDENTIAL_RESOURCE,
resource_id=row.id,
operation="deleted",
principal=principal,
tenant_id=row.tenant_id,
payload={"scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider},
)
for profile in deletion.affected_profiles:
_record_connector_settings_change(
session,
collection=FILES_CONNECTOR_PROFILES_COLLECTION,
resource_type=FILES_CONNECTOR_PROFILE_RESOURCE,
resource_id=profile.id,
operation="updated",
principal=principal,
tenant_id=profile.tenant_id,
payload={
"scope_type": profile.scope_type,
"scope_id": profile.scope_id,
"provider": profile.provider,
"reason": "credential_deleted",
},
)
session.commit()
session.refresh(row)
return _connector_credential_response(row)
except FileStorageError as exc:
session.rollback()
raise _http_error(exc) from exc
except Exception:
session.rollback()
raise
@router.get("/connectors/profiles", response_model=FileConnectorProfilesResponse)
@@ -3248,23 +3277,33 @@ def deactivate_connector_profile(
raise _http_error(exc, not_found=True) from exc
_require_connector_profile_write(principal, row.scope_type)
try:
deactivate_connector_profile_row(session, row, user_id=principal.user.id)
_record_connector_settings_change(
changed = delete_connector_profile_row(
session,
collection=FILES_CONNECTOR_PROFILES_COLLECTION,
resource_type=FILES_CONNECTOR_PROFILE_RESOURCE,
resource_id=row.id,
operation="deleted",
principal=principal,
tenant_id=row.tenant_id,
payload={"scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider},
row,
deletion_reason="api_delete",
user_id=principal.user.id,
api_key_id=principal.api_key.id if principal.api_key else None,
)
if changed:
_record_connector_settings_change(
session,
collection=FILES_CONNECTOR_PROFILES_COLLECTION,
resource_type=FILES_CONNECTOR_PROFILE_RESOURCE,
resource_id=row.id,
operation="deleted",
principal=principal,
tenant_id=row.tenant_id,
payload={"scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider},
)
session.commit()
session.refresh(row)
return FileConnectorProfileResponse(**connector_profile_from_row(row).to_response())
except FileStorageError as exc:
session.rollback()
raise _http_error(exc) from exc
except Exception:
session.rollback()
raise
@router.get("/{file_id}", response_model=FileAssetResponse)