Files
onyx/backend/danswer/server/documents/credential.py
T
hagen-danswer 8d443ada5b Integration tests (#2256)
* initial commit

* almost done

* finished 3 tests

* minor refactor

* built out initial permisison tests

* reworked test_deletion

* removed logging

* all original tests have been converted

* renamed user_groups to user_group

* mypy

* added test for doc set permissions

* unified naming for manager methods

* Refactored models and added new deletion test

* minor additions

* better logging+fixed input variables

* commented out failed tests

* Added readme

* readme update

* Added auth to IT

set auth_type to basic and require_email_verification to false

* Update run-it.yml

* used verify and added to readme

* added api key manager
2024-09-01 22:21:00 +00:00

254 lines
8.3 KiB
Python

from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Query
from sqlalchemy.orm import Session
from danswer.auth.users import current_admin_user
from danswer.auth.users import current_curator_or_admin_user
from danswer.auth.users import current_user
from danswer.db.credentials import alter_credential
from danswer.db.credentials import create_credential
from danswer.db.credentials import CREDENTIAL_PERMISSIONS_TO_IGNORE
from danswer.db.credentials import delete_credential
from danswer.db.credentials import fetch_credential_by_id
from danswer.db.credentials import fetch_credentials
from danswer.db.credentials import fetch_credentials_by_source
from danswer.db.credentials import swap_credentials_connector
from danswer.db.credentials import update_credential
from danswer.db.engine import get_session
from danswer.db.models import DocumentSource
from danswer.db.models import User
from danswer.server.documents.models import CredentialBase
from danswer.server.documents.models import CredentialDataUpdateRequest
from danswer.server.documents.models import CredentialSnapshot
from danswer.server.documents.models import CredentialSwapRequest
from danswer.server.documents.models import ObjectCreationIdResponse
from danswer.server.models import StatusResponse
from danswer.utils.logger import setup_logger
from ee.danswer.db.user_group import validate_user_creation_permissions
logger = setup_logger()
router = APIRouter(prefix="/manage")
def _ignore_credential_permissions(source: DocumentSource) -> bool:
return source in CREDENTIAL_PERMISSIONS_TO_IGNORE
"""Admin-only endpoints"""
@router.get("/admin/credential")
def list_credentials_admin(
user: User | None = Depends(current_curator_or_admin_user),
db_session: Session = Depends(get_session),
) -> list[CredentialSnapshot]:
"""Lists all public credentials"""
credentials = fetch_credentials(
db_session=db_session,
user=user,
get_editable=False,
)
return [
CredentialSnapshot.from_credential_db_model(credential)
for credential in credentials
]
@router.get("/admin/similar-credentials/{source_type}")
def get_cc_source_full_info(
source_type: DocumentSource,
user: User | None = Depends(current_curator_or_admin_user),
db_session: Session = Depends(get_session),
get_editable: bool = Query(
False, description="If true, return editable credentials"
),
) -> list[CredentialSnapshot]:
credentials = fetch_credentials_by_source(
db_session=db_session,
user=user,
document_source=source_type,
get_editable=get_editable,
)
return [
CredentialSnapshot.from_credential_db_model(credential)
for credential in credentials
]
@router.get("/credential/{id}")
def list_credentials_by_id(
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> list[CredentialSnapshot]:
credentials = fetch_credentials(db_session=db_session, user=user)
return [
CredentialSnapshot.from_credential_db_model(credential)
for credential in credentials
]
@router.delete("/admin/credential/{credential_id}")
def delete_credential_by_id_admin(
credential_id: int,
_: User = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> StatusResponse:
"""Same as the user endpoint, but can delete any credential (not just the user's own)"""
delete_credential(db_session=db_session, credential_id=credential_id, user=None)
return StatusResponse(
success=True, message="Credential deleted successfully", data=credential_id
)
@router.put("/admin/credential/swap")
def swap_credentials_for_connector(
credential_swap_req: CredentialSwapRequest,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> StatusResponse:
connector_credential_pair = swap_credentials_connector(
new_credential_id=credential_swap_req.new_credential_id,
connector_id=credential_swap_req.connector_id,
db_session=db_session,
user=user,
)
return StatusResponse(
success=True,
message="Credential swapped successfully",
data=connector_credential_pair.id,
)
@router.post("/credential")
def create_credential_from_model(
credential_info: CredentialBase,
user: User | None = Depends(current_curator_or_admin_user),
db_session: Session = Depends(get_session),
) -> ObjectCreationIdResponse:
if not _ignore_credential_permissions(credential_info.source):
validate_user_creation_permissions(
db_session=db_session,
user=user,
target_group_ids=credential_info.groups,
object_is_public=credential_info.curator_public,
)
credential = create_credential(credential_info, user, db_session)
return ObjectCreationIdResponse(
id=credential.id,
credential=CredentialSnapshot.from_credential_db_model(credential),
)
"""Endpoints for all"""
@router.get("/credential")
def list_credentials(
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> list[CredentialSnapshot]:
credentials = fetch_credentials(db_session=db_session, user=user)
return [
CredentialSnapshot.from_credential_db_model(credential)
for credential in credentials
]
@router.get("/credential/{credential_id}")
def get_credential_by_id(
credential_id: int,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> CredentialSnapshot | StatusResponse[int]:
credential = fetch_credential_by_id(credential_id, user, db_session)
if credential is None:
raise HTTPException(
status_code=401,
detail=f"Credential {credential_id} does not exist or does not belong to user",
)
return CredentialSnapshot.from_credential_db_model(credential)
@router.put("/admin/credential/{credential_id}")
def update_credential_data(
credential_id: int,
credential_update: CredentialDataUpdateRequest,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> CredentialBase:
credential = alter_credential(credential_id, credential_update, user, db_session)
if credential is None:
raise HTTPException(
status_code=401,
detail=f"Credential {credential_id} does not exist or does not belong to user",
)
return CredentialSnapshot.from_credential_db_model(credential)
@router.patch("/credential/{credential_id}")
def update_credential_from_model(
credential_id: int,
credential_data: CredentialBase,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> CredentialSnapshot | StatusResponse[int]:
updated_credential = update_credential(
credential_id, credential_data, user, db_session
)
if updated_credential is None:
raise HTTPException(
status_code=401,
detail=f"Credential {credential_id} does not exist or does not belong to user",
)
return CredentialSnapshot(
source=updated_credential.source,
id=updated_credential.id,
credential_json=updated_credential.credential_json,
user_id=updated_credential.user_id,
name=updated_credential.name,
admin_public=updated_credential.admin_public,
time_created=updated_credential.time_created,
time_updated=updated_credential.time_updated,
curator_public=updated_credential.curator_public,
)
@router.delete("/credential/{credential_id}")
def delete_credential_by_id(
credential_id: int,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> StatusResponse:
delete_credential(
credential_id,
user,
db_session,
)
return StatusResponse(
success=True, message="Credential deleted successfully", data=credential_id
)
@router.delete("/credential/force/{credential_id}")
def force_delete_credential_by_id(
credential_id: int,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> StatusResponse:
delete_credential(credential_id, user, db_session, True)
return StatusResponse(
success=True, message="Credential deleted successfully", data=credential_id
)