from __future__ import annotations from collections.abc import Callable, Iterable from dataclasses import replace from sqlalchemy.orm import Session from govoplan_files.backend.storage.connector_deployment import ( connector_effective_endpoint_url, ) from govoplan_files.backend.storage.connector_profile_store import ( select_database_connector_profiles, ) from govoplan_files.backend.storage.connector_profiles import ( ConnectorProfile, connector_profiles_from_settings, ) from govoplan_files.backend.storage.connector_policy import ( ConnectorAccessRequest, connector_policy_decision, ) from govoplan_files.backend.storage.connector_policy_store import ( effective_connector_policy_sources, ) from govoplan_files.backend.storage.connector_providers import ( connector_provider_descriptors, ) CampaignVisibility = Callable[[str], bool] def visible_connector_profiles_for_actor( session: Session, *, tenant_id: str, user_id: str, group_ids: Iterable[str], settings: object | None, provider: str | None = None, campaign_id: str | None = None, campaign_visible: CampaignVisibility | None = None, include_disabled: bool = False, include_admin_scopes: bool = False, include_effective_policy: bool = True, ) -> list[ConnectorProfile]: """Return the same actor-filtered connector set used by API and docs surfaces.""" provider_norm = provider.strip().casefold() if provider else None actor_group_ids = {str(group_id) for group_id in group_ids if str(group_id)} database_profiles, database_profile_ids = select_database_connector_profiles( session, tenant_id=tenant_id, include_disabled=include_disabled, row_visible=lambda row: ( include_admin_scopes and row.scope_type in {"user", "group", "campaign"} ) or _scope_visible_to_actor( scope_type=row.scope_type, scope_id=row.scope_id, tenant_id=tenant_id, user_id=user_id, group_ids=actor_group_ids, campaign_id=campaign_id, campaign_visible=campaign_visible, ), ) configured_profiles = connector_profiles_from_settings(settings) profiles_by_id: dict[str, ConnectorProfile] = {} for profile in database_profiles: if profile.id not in profiles_by_id: profiles_by_id[profile.id] = profile for profile in configured_profiles: if profile.id not in database_profile_ids and profile.id not in profiles_by_id: profiles_by_id[profile.id] = profile visible: list[ConnectorProfile] = [] for profile in profiles_by_id.values(): if not (profile.enabled or include_disabled): continue if provider_norm is not None and profile.provider != provider_norm: continue admin_scope_visible = include_admin_scopes and profile.scope_type in { "user", "group", "campaign", } if not admin_scope_visible and not _profile_visible_to_actor( profile, tenant_id=tenant_id, user_id=user_id, group_ids=actor_group_ids, campaign_id=campaign_id, campaign_visible=campaign_visible, ): continue visible.append( _with_effective_connector_policy( session, tenant_id=tenant_id, profile=profile ) if include_effective_policy else profile ) return visible def connector_profile_usable_for_import(profile: ConnectorProfile) -> bool: """Whether a visible profile can safely offer the current live browse/import task.""" effective_endpoint_url = connector_effective_endpoint_url( provider=profile.provider, endpoint_url=profile.endpoint_url, metadata=profile.metadata, ) if ( not profile.enabled or not effective_endpoint_url or not profile.credentials_configured or bool(profile.secret_ref) ): return False descriptor = next( ( item for item in connector_provider_descriptors() if item.provider == profile.provider ), None, ) if descriptor is None: return False if not ( descriptor.implemented and descriptor.installed and descriptor.browse_supported and descriptor.import_supported ): return False # These SDK paths intentionally fail closed until every connection peer and # SDK-managed redirect/referral can be pinned and revalidated. if profile.provider in {"s3", "smb"}: return False # Prove that the initial root browse performed by the current Files UI is # policy-allowed. A later selected remote path/item is checked again. return connector_policy_decision( ConnectorAccessRequest( connector_id=profile.id, credential_id=profile.credential_profile_id, provider=profile.provider, external_path="", external_url=effective_endpoint_url, operation="import", ), profile.policy_sources, ).allowed def _profile_visible_to_actor( profile: ConnectorProfile, *, tenant_id: str, user_id: str, group_ids: set[str], campaign_id: str | None, campaign_visible: CampaignVisibility | None, ) -> bool: return _scope_visible_to_actor( scope_type=profile.scope_type, scope_id=profile.scope_id, tenant_id=tenant_id, user_id=user_id, group_ids=group_ids, campaign_id=campaign_id, campaign_visible=campaign_visible, ) def _scope_visible_to_actor( *, scope_type: str, scope_id: str | None, tenant_id: str, user_id: str, group_ids: set[str], campaign_id: str | None, campaign_visible: CampaignVisibility | None, ) -> bool: if scope_type == "system": return True if scope_type == "tenant": return scope_id == tenant_id if scope_type == "user": return scope_id == user_id if scope_type == "group": return bool(scope_id and scope_id in group_ids) if scope_type != "campaign" or not scope_id: return False if campaign_id and scope_id != campaign_id: return False return bool(campaign_visible and campaign_visible(scope_id)) def _with_effective_connector_policy( session: Session, *, tenant_id: str, profile: ConnectorProfile, ) -> ConnectorProfile: sources = effective_connector_policy_sources( session, tenant_id=tenant_id, scope_type=profile.scope_type, scope_id=profile.scope_id, ) if not sources: return profile return replace(profile, policy_sources=tuple([*sources, *profile.policy_sources]))