Skip to content

Commit

Permalink
Make curators able to create permission synced connectors
Browse files Browse the repository at this point in the history
  • Loading branch information
hagen-danswer committed Nov 13, 2024
1 parent 6066042 commit c33029d
Show file tree
Hide file tree
Showing 17 changed files with 87 additions and 55 deletions.
2 changes: 2 additions & 0 deletions backend/danswer/db/connector_credential_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ def _add_user_filters(
.where(~UG__CCpair.user_group_id.in_(user_groups))
.correlate(ConnectorCredentialPair)
)
where_clause |= ConnectorCredentialPair.access_type == AccessType.SYNC
else:
where_clause |= ConnectorCredentialPair.access_type == AccessType.PUBLIC
where_clause |= ConnectorCredentialPair.access_type == AccessType.SYNC

return stmt.where(where_clause)

Expand Down
1 change: 1 addition & 0 deletions backend/danswer/server/documents/cc_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ def associate_credential_to_connector(
user=user,
target_group_ids=metadata.groups,
object_is_public=metadata.access_type == AccessType.PUBLIC,
object_is_perm_sync=metadata.access_type == AccessType.SYNC,
)

try:
Expand Down
44 changes: 22 additions & 22 deletions backend/danswer/server/documents/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
from danswer.db.models import IndexingStatus
from danswer.db.models import SearchSettings
from danswer.db.models import User
from danswer.db.models import UserRole
from danswer.db.search_settings import get_current_search_settings
from danswer.db.search_settings import get_secondary_search_settings
from danswer.file_store.file_store import get_default_file_store
Expand Down Expand Up @@ -665,7 +664,8 @@ def create_connector_from_model(
db_session=db_session,
user=user,
target_group_ids=connector_data.groups,
object_is_public=connector_data.is_public,
object_is_public=connector_data.access_type == AccessType.PUBLIC,
object_is_perm_sync=connector_data.access_type == AccessType.SYNC,
)
connector_base = connector_data.to_connector_base()
return create_connector(
Expand All @@ -683,40 +683,39 @@ def create_connector_with_mock_credential(
user: User = Depends(current_curator_or_admin_user),
db_session: Session = Depends(get_session),
) -> StatusResponse:
if user and user.role != UserRole.ADMIN:
if connector_data.is_public:
raise HTTPException(
status_code=401,
detail="User does not have permission to create public credentials",
)
if not connector_data.groups:
raise HTTPException(
status_code=401,
detail="Curators must specify 1+ groups",
)
fetch_ee_implementation_or_noop(
"danswer.db.user_group", "validate_user_creation_permissions", None
)(
db_session=db_session,
user=user,
target_group_ids=connector_data.groups,
object_is_public=connector_data.access_type == AccessType.PUBLIC,
object_is_perm_sync=connector_data.access_type == AccessType.SYNC,
)
try:
_validate_connector_allowed(connector_data.source)
connector_response = create_connector(
db_session=db_session, connector_data=connector_data
db_session=db_session,
connector_data=connector_data,
)

mock_credential = CredentialBase(
credential_json={}, admin_public=True, source=connector_data.source
credential_json={},
admin_public=True,
source=connector_data.source,
)
credential = create_credential(
mock_credential, user=user, db_session=db_session
)

access_type = (
AccessType.PUBLIC if connector_data.is_public else AccessType.PRIVATE
credential_data=mock_credential,
user=user,
db_session=db_session,
)

response = add_credential_to_connector(
db_session=db_session,
user=user,
connector_id=cast(int, connector_response.id), # will aways be an int
credential_id=credential.id,
access_type=access_type,
access_type=connector_data.access_type,
cc_pair_name=connector_data.name,
groups=connector_data.groups,
)
Expand All @@ -741,7 +740,8 @@ def update_connector_from_model(
db_session=db_session,
user=user,
target_group_ids=connector_data.groups,
object_is_public=connector_data.is_public,
object_is_public=connector_data.access_type == AccessType.PUBLIC,
object_is_perm_sync=connector_data.access_type == AccessType.SYNC,
)
connector_base = connector_data.to_connector_base()
except ValueError as e:
Expand Down
4 changes: 2 additions & 2 deletions backend/danswer/server/documents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ class ConnectorBase(BaseModel):


class ConnectorUpdateRequest(ConnectorBase):
is_public: bool = True
access_type: AccessType
groups: list[int] = Field(default_factory=list)

def to_connector_base(self) -> ConnectorBase:
return ConnectorBase(**self.model_dump(exclude={"is_public", "groups"}))
return ConnectorBase(**self.model_dump(exclude={"access_type", "groups"}))


class ConnectorSnapshot(ConnectorBase):
Expand Down
9 changes: 7 additions & 2 deletions backend/ee/danswer/db/user_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,21 @@ def _cleanup_document_set__user_group_relationships__no_commit(
def validate_user_creation_permissions(
db_session: Session,
user: User | None,
target_group_ids: list[int] | None,
object_is_public: bool | None,
target_group_ids: list[int] | None = None,
object_is_public: bool | None = None,
object_is_perm_sync: bool | None = None,
) -> None:
"""
All users can create/edit permission synced objects if they don't specify a group
All admin actions are allowed.
Prevents non-admins from creating/editing:
- public objects
- objects with no groups
- objects that belong to a group they don't curate
"""
if object_is_perm_sync and not target_group_ids:
return

if not user or user.role == UserRole.ADMIN:
return

Expand Down
6 changes: 4 additions & 2 deletions web/src/app/admin/connector/[ccPairId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,11 @@ function Main({ ccPairId }: { ccPairId: number }) {
</div>
{!ccPair.is_editable_for_current_user && (
<div className="text-sm mt-2 text-neutral-500 italic">
{ccPair.is_public
{ccPair.access_type === "public"
? "Public connectors are not editable by curators."
: "This connector belongs to groups where you don't have curator permissions, so it's not editable."}
: ccPair.access_type === "sync"
? "Sync connectors are not editable by curators."
: "This connector belongs to groups where you don't have curator permissions, so it's not editable."}
</div>
)}

Expand Down
3 changes: 2 additions & 1 deletion web/src/app/admin/connector/[ccPairId]/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DeletionAttemptSnapshot,
IndexAttemptSnapshot,
ValidStatuses,
AccessType,
} from "@/lib/types";

export enum ConnectorCredentialPairStatus {
Expand All @@ -22,7 +23,7 @@ export interface CCPairFullInfo {
number_of_index_attempts: number;
last_index_attempt_status: ValidStatuses | null;
latest_deletion_attempt: DeletionAttemptSnapshot | null;
is_public: boolean;
access_type: AccessType;
is_editable_for_current_user: boolean;
deletion_failure_message: string | null;
indexing: boolean;
Expand Down
17 changes: 9 additions & 8 deletions web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ const BASE_CONNECTOR_URL = "/api/manage/admin/connector";
export async function submitConnector<T>(
connector: ConnectorBase<T>,
connectorId?: number,
fakeCredential?: boolean,
isPublicCcpair?: boolean // exclusively for mock credentials, when also need to specify ccpair details
fakeCredential?: boolean
): Promise<{ message: string; isSuccess: boolean; response?: Connector<T> }> {
const isUpdate = connectorId !== undefined;
if (!connector.connector_specific_config) {
connector.connector_specific_config = {} as T;
}
console.log(connector);

try {
if (fakeCredential) {
Expand All @@ -71,7 +71,7 @@ export async function submitConnector<T>(
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...connector, is_public: isPublicCcpair }),
body: JSON.stringify({ ...connector }),
}
);
if (response.ok) {
Expand Down Expand Up @@ -112,6 +112,7 @@ export default function AddConnector({
connector: ConfigurableSources;
}) {
const router = useRouter();
console.log(connector);

// State for managing credentials and files
const [currentCredential, setCurrentCredential] =
Expand Down Expand Up @@ -268,7 +269,7 @@ export default function AddConnector({
advancedConfiguration.refreshFreq,
advancedConfiguration.pruneFreq,
advancedConfiguration.indexingStart,
values.access_type == "public",
values.access_type,
groups,
name
);
Expand All @@ -285,30 +286,30 @@ export default function AddConnector({
setPopup,
setSelectedFiles,
name,
access_type == "public",
access_type,
groups
);
if (response) {
onSuccess();
}
return;
}
console.log(connector);

const { message, isSuccess, response } = await submitConnector<any>(
{
connector_specific_config: transformedConnectorSpecificConfig,
input_type: isLoadState(connector) ? "load_state" : "poll", // single case
name: name,
source: connector,
is_public: access_type == "public",
access_type: access_type,
refresh_freq: advancedConfiguration.refreshFreq || null,
prune_freq: advancedConfiguration.pruneFreq || null,
indexing_start: advancedConfiguration.indexingStart || null,
groups: groups,
},
undefined,
credentialActivated ? false : true,
access_type == "public"
credentialActivated ? false : true
);
// If no credential
if (!credentialActivated) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const DynamicConnectionForm: FC<DynamicConnectionFormProps> = ({
)}

<AccessTypeForm connector={connector} />
<AccessTypeGroupSelector />
<AccessTypeGroupSelector connector={connector} />

{config.advanced_values.length > 0 && (
<>
Expand Down
9 changes: 5 additions & 4 deletions web/src/app/admin/connectors/[connector]/pages/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { PopupSpec } from "@/components/admin/connectors/Popup";
import { createConnector, runConnector } from "@/lib/connector";
import { createCredential, linkCredential } from "@/lib/credential";
import { FileConfig } from "@/lib/connectors/connectors";
import { AccessType } from "@/lib/types";

export const submitFiles = async (
selectedFiles: File[],
setPopup: (popup: PopupSpec) => void,
setSelectedFiles: (files: File[]) => void,
name: string,
isPublic: boolean,
access_type: string,
groups?: number[]
) => {
const formData = new FormData();
Expand Down Expand Up @@ -42,7 +43,7 @@ export const submitFiles = async (
refresh_freq: null,
prune_freq: null,
indexing_start: null,
is_public: isPublic,
access_type: access_type,
groups: groups,
});
if (connectorErrorMsg || !connector) {
Expand All @@ -61,7 +62,7 @@ export const submitFiles = async (
credential_json: {},
admin_public: true,
source: "file",
curator_public: isPublic,
curator_public: true,
groups: groups,
name,
});
Expand All @@ -80,7 +81,7 @@ export const submitFiles = async (
connector.id,
credentialId,
name,
isPublic ? "public" : "private",
access_type as AccessType,
groups
);
if (!credentialResponse.ok) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const submitGoogleSite = async (
refreshFreq: number,
pruneFreq: number,
indexingStart: Date,
is_public: boolean,
access_type: string,
groups: number[],
name?: string
) => {
Expand Down Expand Up @@ -44,7 +44,7 @@ export const submitGoogleSite = async (
base_url: base_url,
zip_path: filePaths[0],
},
is_public: is_public,
access_type: access_type,
refresh_freq: refreshFreq,
prune_freq: pruneFreq,
indexing_start: indexingStart,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ export function CCPairIndexingStatusTable({
indexing_start: new Date("2023-07-01T12:00:00Z"),
id: 1,
credential_ids: [],
access_type: "public",
time_created: "2023-07-01T12:00:00Z",
time_updated: "2023-07-01T12:00:00Z",
},
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/IsPublicGroupSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
const { isAdmin, user, isLoadingUser, isCurator } = useUser();
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
const [shouldHideContent, setShouldHideContent] = useState(false);

console.log(formikProps.values);
useEffect(() => {
if (user && userGroups && isPaidEnterpriseFeaturesEnabled) {
const isUserAdmin = user.role === UserRole.ADMIN;
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/admin/connectors/AccessTypeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function AccessTypeForm({
});
}

if (isAutoSyncSupported && isAdmin && isPaidEnterpriseEnabled) {
if (isAutoSyncSupported && isPaidEnterpriseEnabled) {
options.push({
name: "Auto Sync Permissions",
value: "sync",
Expand All @@ -73,7 +73,7 @@ export function AccessTypeForm({

return (
<>
{isPaidEnterpriseEnabled && isAdmin && (
{isPaidEnterpriseEnabled && (isAdmin || isAutoSyncSupported) && (
<>
<div>
<label className="text-text-950 font-medium">Document Access</label>
Expand Down
Loading

0 comments on commit c33029d

Please sign in to comment.