Skip to content

Commit

Permalink
Make public credentials accessible by all admins (#337)
Browse files Browse the repository at this point in the history
  • Loading branch information
Weves authored Aug 26, 2023
1 parent b27107c commit 642862b
Show file tree
Hide file tree
Showing 20 changed files with 290 additions and 201 deletions.
9 changes: 6 additions & 3 deletions backend/danswer/db/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@


def fetch_credentials(
user: User | None,
db_session: Session,
user: User | None = None,
public_only: bool | None = None,
) -> list[Credential]:
stmt = select(Credential)
if user:
stmt = stmt.where(
or_(Credential.user_id == user.id, Credential.user_id.is_(None))
)
if public_only is not None:
stmt = stmt.where(Credential.public_doc == public_only)
results = db_session.scalars(stmt)
return list(results.all())

Expand Down Expand Up @@ -106,7 +109,7 @@ def backend_update_credential_json(

def delete_credential(
credential_id: int,
user: User,
user: User | None,
db_session: Session,
) -> None:
credential = fetch_credential_by_id(credential_id, user, db_session)
Expand Down Expand Up @@ -146,7 +149,7 @@ def create_initial_public_credential() -> None:
def delete_google_drive_service_account_credentials(
user: User | None, db_session: Session
) -> None:
credentials = fetch_credentials(user, db_session)
credentials = fetch_credentials(db_session=db_session, user=user)
for credential in credentials:
if credential.credential_json.get(DB_CREDENTIALS_DICT_SERVICE_ACCOUNT_KEY):
db_session.delete(credential)
Expand Down
2 changes: 2 additions & 0 deletions backend/danswer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from danswer.datastores.document_index import get_default_document_index
from danswer.db.credentials import create_initial_public_credential
from danswer.direct_qa.llm_utils import get_default_llm
from danswer.server.credential import router as credential_router
from danswer.server.event_loading import router as event_processing_router
from danswer.server.health import router as health_router
from danswer.server.manage import router as admin_router
Expand Down Expand Up @@ -68,6 +69,7 @@ def get_application() -> FastAPI:
application.include_router(event_processing_router)
application.include_router(admin_router)
application.include_router(user_router)
application.include_router(credential_router)
application.include_router(health_router)

application.include_router(
Expand Down
128 changes: 128 additions & 0 deletions backend/danswer/server/credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session

from danswer.auth.users import current_admin_user
from danswer.auth.users import current_user
from danswer.db.credentials import create_credential
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 update_credential
from danswer.db.engine import get_session
from danswer.db.models import User
from danswer.server.models import CredentialBase
from danswer.server.models import CredentialSnapshot
from danswer.server.models import ObjectCreationIdResponse
from danswer.server.models import StatusResponse


router = APIRouter(prefix="/manage")


"""Admin-only endpoints"""


@router.get("/admin/credential")
def list_credentials_admin(
_: User = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[CredentialSnapshot]:
"""Lists all public credentials"""
credentials = fetch_credentials(db_session=db_session, public_only=True)
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
)


"""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.post("/credential")
def create_credential_from_model(
connector_info: CredentialBase,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> ObjectCreationIdResponse:
return create_credential(connector_info, user, db_session)


@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(
id=updated_credential.id,
credential_json=updated_credential.credential_json,
user_id=updated_credential.user_id,
public_doc=updated_credential.public_doc,
time_created=updated_credential.time_created,
time_updated=updated_credential.time_updated,
)


@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
)
75 changes: 0 additions & 75 deletions backend/danswer/server/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,81 +641,6 @@ def get_connector_by_id(
)


@router.get("/credential")
def get_credentials(
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> list[CredentialSnapshot]:
credentials = fetch_credentials(user, db_session)
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.post("/credential")
def create_credential_from_model(
connector_info: CredentialBase,
user: User = Depends(current_user),
db_session: Session = Depends(get_session),
) -> ObjectCreationIdResponse:
return create_credential(connector_info, user, db_session)


@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(
id=updated_credential.id,
credential_json=updated_credential.credential_json,
user_id=updated_credential.user_id,
public_doc=updated_credential.public_doc,
time_created=updated_credential.time_created,
time_updated=updated_credential.time_updated,
)


@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.put("/connector/{connector_id}/credential/{credential_id}")
def associate_credential_to_connector(
connector_id: int,
Expand Down
16 changes: 7 additions & 9 deletions web/src/app/admin/connectors/bookstack/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
import {
BookstackCredentialJson,
BookstackConfig,
Credential,
ConnectorIndexingStatus,
} from "@/lib/types";
import useSWR, { useSWRConfig } from "swr";
import { fetcher } from "@/lib/fetcher";
import { LoadingAnimation } from "@/components/Loading";
import { deleteCredential, linkCredential } from "@/lib/credential";
import { adminDeleteCredential, linkCredential } from "@/lib/credential";
import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
import { usePopup } from "@/components/admin/connectors/Popup";
import { usePublicCredentials } from "@/lib/hooks";

const Main = () => {
const { popup, setPopup } = usePopup();
Expand All @@ -35,10 +35,8 @@ const Main = () => {
data: credentialsData,
isLoading: isCredentialsLoading,
error: isCredentialsError,
} = useSWR<Credential<BookstackCredentialJson>[]>(
"/api/manage/credential",
fetcher
);
refreshCredentials,
} = usePublicCredentials();

if (
(!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
Expand Down Expand Up @@ -91,8 +89,8 @@ const Main = () => {
});
return;
}
await deleteCredential(bookstackCredential.id);
mutate("/api/manage/credential");
await adminDeleteCredential(bookstackCredential.id);
refreshCredentials();
}}
>
<TrashIcon />
Expand Down Expand Up @@ -146,7 +144,7 @@ const Main = () => {
}}
onSubmit={(isSuccess) => {
if (isSuccess) {
mutate("/api/manage/credential");
refreshCredentials();
mutate("/api/manage/admin/connector/indexing-status");
}
}}
Expand Down
16 changes: 7 additions & 9 deletions web/src/app/admin/connectors/confluence/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
import {
ConfluenceCredentialJson,
ConfluenceConfig,
Credential,
ConnectorIndexingStatus,
} from "@/lib/types";
import useSWR, { useSWRConfig } from "swr";
import { fetcher } from "@/lib/fetcher";
import { LoadingAnimation } from "@/components/Loading";
import { deleteCredential, linkCredential } from "@/lib/credential";
import { adminDeleteCredential, linkCredential } from "@/lib/credential";
import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
import { usePopup } from "@/components/admin/connectors/Popup";
import { usePublicCredentials } from "@/lib/hooks";

const Main = () => {
const { popup, setPopup } = usePopup();
Expand All @@ -35,10 +35,8 @@ const Main = () => {
data: credentialsData,
isLoading: isCredentialsLoading,
error: isCredentialsError,
} = useSWR<Credential<ConfluenceCredentialJson>[]>(
"/api/manage/credential",
fetcher
);
refreshCredentials,
} = usePublicCredentials();

if (
(!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
Expand Down Expand Up @@ -97,8 +95,8 @@ const Main = () => {
});
return;
}
await deleteCredential(confluenceCredential.id);
mutate("/api/manage/credential");
await adminDeleteCredential(confluenceCredential.id);
refreshCredentials();
}}
>
<TrashIcon />
Expand Down Expand Up @@ -143,7 +141,7 @@ const Main = () => {
}}
onSubmit={(isSuccess) => {
if (isSuccess) {
mutate("/api/manage/credential");
refreshCredentials();
}
}}
/>
Expand Down
Loading

1 comment on commit 642862b

@vercel
Copy link

@vercel vercel bot commented on 642862b Aug 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.