satupeta-main/app/services/credential_service.py

304 lines
11 KiB
Python
Raw Normal View History

2026-01-27 02:11:58 +00:00
from typing import Any, Dict, List, Optional, Tuple
from uuid import UUID
from fastapi import HTTPException, status
from pydantic import ValidationError
from app.core.exceptions import NotFoundException
from app.models.credential_model import CredentialModel
from app.repositories.credential_repository import CredentialRepository
from app.schemas.user_schema import UserSchema
from app.utils.encryption import credential_encryption
from . import BaseService
class CredentialService(BaseService[CredentialModel, CredentialRepository]):
def __init__(self, repository: CredentialRepository):
super().__init__(CredentialModel, repository)
async def create_credential(
self,
name: str,
credential_type: str,
sensitive_data: Dict[str, Any],
credential_metadata: Optional[Dict[str, Any]] = None,
description: Optional[str] = None,
is_default: bool = False,
user_id: UUID = None,
) -> CredentialModel:
"""
Buat kredensial baru dengan mengenkripsi data sensitif.
Args:
name: Nama kredensial
credential_type: Tipe kredensial ('database', 'api', 'minio', dll)
sensitive_data: Data sensitif yang akan dienkripsi
credential_metadata: metadata tidak sensitif (opsional)
description: Deskripsi kredensial (opsional)
is_default: Apakah kredensial ini default untuk tipenya
user_id: ID user yang membuat kredensial
Returns:
Credential model yang telah disimpan
"""
encrypted_data, encryption_iv = credential_encryption.encrypt(sensitive_data)
credential_data = {
"name": name,
"description": description,
"credential_type": credential_type,
"encrypted_data": encrypted_data,
"encryption_iv": encryption_iv,
"credential_metadata": credential_metadata or {},
"is_default": is_default,
"created_by": user_id,
"updated_by": user_id,
}
return await self.repository.create(credential_data)
async def get_credential_with_decrypted_data(self, credential_id: UUID) -> Dict[str, Any]:
"""
Ambil kredensial beserta data sensitif yang sudah didekripsi.
Args:
credential_id: ID kredensial
Returns:
Tuple dari (credential_model, decrypted_data)
"""
credential = await self.find_by_id(credential_id)
if not credential:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Credential not found")
decrypted_data = credential_encryption.decrypt(credential.encrypted_data, credential.encryption_iv)
credential_dict = credential.to_dict()
credential_dict["decrypted_data"] = decrypted_data
return credential_dict
async def get_list_of_decrypted_credentials(
self, filters: list, sort: list = [], search: str = "", group_by: str = None, limit: int = 100, offset: int = 0
) -> Tuple[List[Dict[str, Any]], int]:
"""
Ambil kredensial beserta data sensitif yang sudah didekripsi.
Args:
credential_id: ID kredensial
Returns:
Tuple dari (credential_model, decrypted_data)
"""
credentials, total = await self.find_all(
filters=filters, sort=sort, search=search, group_by=group_by, limit=limit, offset=offset
)
decrypted_credentials = []
for credential in credentials:
try:
decrypted_data = credential_encryption.decrypt(credential.encrypted_data, credential.encryption_iv)
temp = credential.to_dict()
temp["decrypted_data"] = decrypted_data
decrypted_credentials.append(temp)
except ValidationError:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to decrypt some credential data"
)
return decrypted_credentials, total
async def update_credential(self, credential_id: UUID, data: Dict[str, Any], user_id: UUID) -> CredentialModel:
"""
Update kredensial.
Args:
credential_id: ID kredensial yang akan diupdate
data: Data yang akan diupdate (dapat berisi 'sensitive_data', 'credential_metadata', dll)
user_id: ID user yang melakukan update
Returns:
Updated credential model
"""
credential = await self.find_by_id(credential_id)
if not credential:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Credential not found")
if "credential_metadata" in data:
if not isinstance(data["credential_metadata"], dict):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="credential_metadata must be a dictionary"
)
metadata = credential.credential_metadata
metadata.update(data["credential_metadata"])
data["credential_metadata"] = metadata
for key, value in data.items():
if key != "sensitive_data" and hasattr(credential, key):
setattr(credential, key, value)
if "sensitive_data" in data and data["sensitive_data"]:
current_data = credential_encryption.decrypt(credential.encrypted_data, credential.encryption_iv)
current_data.update(data["sensitive_data"])
encrypted_data, encryption_iv = credential_encryption.encrypt(current_data)
credential.encrypted_data = encrypted_data
credential.encryption_iv = encryption_iv
credential.updated_by = user_id
if data.get("is_default", False) and not credential.is_default:
await self.repository.set_default(credential_id, user_id)
return await self.repository.update(credential_id, credential.to_dict())
async def get_credentials_by_type(self, credential_type: str, is_active: bool = True) -> List[CredentialModel]:
"""
Dapatkan semua kredensial berdasarkan tipe.
Args:
credential_type: Tipe kredensial
is_active: Filter berdasarkan status aktif
Returns:
List dari credential models
"""
return await self.repository.get_by_type(credential_type, is_active)
async def get_default_credential(
self, credential_type: str, with_decrypted_data: bool = False
) -> Tuple[Optional[CredentialModel], Optional[Dict[str, Any]]]:
"""
Dapatkan kredensial default berdasarkan tipe.
Args:
db: Database session
credential_type: Tipe kredensial
with_decrypted_data: Apakah akan mendekripsi data sensitif
Returns:
Tuple dari (credential_model, decrypted_data)
"""
credential = await self.repository.get_default_by_type(credential_type, is_active=True)
if not credential:
return None, None
if with_decrypted_data:
try:
decrypted_data = credential_encryption.decrypt(credential.encrypted_data, credential.encryption_iv)
return credential, decrypted_data
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to decrypt credential data"
)
return credential, None
async def delete(self, user: UserSchema, id: UUID) -> None:
credential = await self.find_by_id(id)
if not credential:
raise NotFoundException(f"Credential with ID {id} not found")
delete_by_dict = {"is_deleted": True, "is_active": False, "updated_by": user.id}
await self.repository.update(id, delete_by_dict)
async def test_credential(self, credential_id: UUID, user_id: UUID) -> Dict[str, Any]:
"""
Test koneksi menggunakan kredensial.
Implementasi akan berbeda tergantung tipe kredensial.
Args:
db: Database session
credential_id: ID kredensial yang akan ditest
user_id: ID user yang melakukan test
Returns:
Dictionary berisi hasil test
"""
# Dapatkan kredensial dengan data terdekripsi
credential, decrypted_data = await self.get_credential_with_decrypted_data(credential_id)
result = {"success": False, "details": {}}
try:
# Lakukan test berdasarkan tipe kredensial
if credential.credential_type == "database":
# Implementasi test untuk database
result = await self._test_database_credential(decrypted_data)
elif credential.credential_type == "minio":
# Implementasi test untuk MinIO
result = await self._test_minio_credential(decrypted_data)
elif credential.credential_type == "api":
# Implementasi test untuk API
result = await self._test_api_credential(decrypted_data)
else:
result = {
"success": False,
"details": {"message": f"Testing for type {credential.credential_type} not implemented"},
}
# Update timestamp penggunaan terakhir
await self.repository.update_last_used(credential_id, user_id)
except Exception as e:
result = {"success": False, "details": {"error": str(e)}}
return result
async def _test_database_credential(self, cred_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Test koneksi database.
Args:
cred_data: Data kredensial yang sudah didekripsi
Returns:
Dictionary berisi hasil test
"""
# Implementasi test koneksi database
# Contoh:
try:
# Simulasi test
# Dalam implementasi sebenarnya, lakukan koneksi ke database
return {"success": True, "details": {"message": "Database connection successful"}}
except Exception as e:
return {"success": False, "details": {"error": str(e)}}
async def _test_minio_credential(self, cred_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Test koneksi MinIO.
Args:
cred_data: Data kredensial yang sudah didekripsi
Returns:
Dictionary berisi hasil test
"""
try:
# Implementasi test koneksi MinIO
# Di sini bisa menggunakan miniopy-async untuk tes koneksi
return {"success": True, "details": {"message": "MinIO connection successful"}}
except Exception as e:
return {"success": False, "details": {"error": str(e)}}
async def _test_api_credential(self, cred_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Test koneksi API.
Args:
cred_data: Data kredensial yang sudah didekripsi
Returns:
Dictionary berisi hasil test
"""
try:
# Implementasi test koneksi API
# Di sini bisa menggunakan aiohttp untuk tes koneksi
return {"success": True, "details": {"message": "API connection successful"}}
except Exception as e:
return {"success": False, "details": {"error": str(e)}}