219 lines
6.7 KiB
Python
219 lines
6.7 KiB
Python
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]))
|