refactor(files): centralize connector visibility
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from dataclasses import replace
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
@@ -131,11 +130,11 @@ from govoplan_files.backend.storage.connector_profile_store import (
|
|||||||
connector_profile_from_row,
|
connector_profile_from_row,
|
||||||
create_connector_profile_row,
|
create_connector_profile_row,
|
||||||
get_connector_profile_row,
|
get_connector_profile_row,
|
||||||
list_database_connector_profiles,
|
|
||||||
update_connector_profile_row,
|
update_connector_profile_row,
|
||||||
)
|
)
|
||||||
from govoplan_files.backend.storage.connector_profiles import ConnectorProfile, connector_profiles_from_settings
|
from govoplan_files.backend.storage.connector_profiles import ConnectorProfile
|
||||||
from govoplan_files.backend.storage.connector_providers import connector_provider_descriptors
|
from govoplan_files.backend.storage.connector_providers import connector_provider_descriptors
|
||||||
|
from govoplan_files.backend.storage.connector_visibility import visible_connector_profiles_for_actor
|
||||||
from govoplan_files.backend.storage.connector_spaces import (
|
from govoplan_files.backend.storage.connector_spaces import (
|
||||||
connector_space_owner_id,
|
connector_space_owner_id,
|
||||||
create_connector_space,
|
create_connector_space,
|
||||||
@@ -455,35 +454,6 @@ def _enforce_connector_policy(source_provenance_json: str | None, connector_poli
|
|||||||
ensure_connector_policy_allows(request, sources)
|
ensure_connector_policy_allows(request, sources)
|
||||||
|
|
||||||
|
|
||||||
def _connector_profile_visible(
|
|
||||||
session: Session,
|
|
||||||
principal: ApiPrincipal,
|
|
||||||
profile: ConnectorProfile,
|
|
||||||
*,
|
|
||||||
campaign_id: str | None,
|
|
||||||
group_ids: set[str],
|
|
||||||
) -> bool:
|
|
||||||
if profile.scope_type == "system":
|
|
||||||
return True
|
|
||||||
if profile.scope_type == "tenant":
|
|
||||||
return profile.scope_id == principal.tenant_id
|
|
||||||
if profile.scope_type == "user":
|
|
||||||
return profile.scope_id == principal.user.id
|
|
||||||
if profile.scope_type == "group":
|
|
||||||
return bool(profile.scope_id and profile.scope_id in group_ids)
|
|
||||||
if profile.scope_type == "campaign":
|
|
||||||
if not profile.scope_id:
|
|
||||||
return False
|
|
||||||
if campaign_id and profile.scope_id != campaign_id:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
_ensure_campaign_file_access(session, principal, profile.scope_id)
|
|
||||||
except HTTPException:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _visible_connector_profiles(
|
def _visible_connector_profiles(
|
||||||
session: Session,
|
session: Session,
|
||||||
principal: ApiPrincipal,
|
principal: ApiPrincipal,
|
||||||
@@ -494,30 +464,33 @@ def _visible_connector_profiles(
|
|||||||
include_admin_scopes: bool = False,
|
include_admin_scopes: bool = False,
|
||||||
include_effective_policy: bool = True,
|
include_effective_policy: bool = True,
|
||||||
) -> list[ConnectorProfile]:
|
) -> list[ConnectorProfile]:
|
||||||
provider_norm = provider.strip().casefold() if provider else None
|
group_ids = user_group_ids(
|
||||||
group_ids = set(user_group_ids(session, tenant_id=principal.tenant_id, user_id=principal.user.id, include_admin_groups=_is_admin(principal)))
|
|
||||||
database_profiles = list_database_connector_profiles(
|
|
||||||
session,
|
session,
|
||||||
tenant_id=principal.tenant_id,
|
tenant_id=principal.tenant_id,
|
||||||
include_disabled=include_disabled,
|
user_id=principal.user.id,
|
||||||
|
include_admin_groups=_is_admin(principal),
|
||||||
|
)
|
||||||
|
|
||||||
|
def campaign_visible(profile_campaign_id: str) -> bool:
|
||||||
|
try:
|
||||||
|
_ensure_campaign_file_access(session, principal, profile_campaign_id)
|
||||||
|
except HTTPException:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
return visible_connector_profiles_for_actor(
|
||||||
|
session,
|
||||||
|
tenant_id=principal.tenant_id,
|
||||||
|
user_id=principal.user.id,
|
||||||
|
group_ids=group_ids,
|
||||||
|
settings=settings,
|
||||||
|
provider=provider,
|
||||||
|
campaign_id=campaign_id,
|
||||||
|
campaign_visible=campaign_visible,
|
||||||
|
include_disabled=include_disabled,
|
||||||
|
include_admin_scopes=include_admin_scopes,
|
||||||
|
include_effective_policy=include_effective_policy,
|
||||||
)
|
)
|
||||||
env_profiles = connector_profiles_from_settings(settings)
|
|
||||||
profiles_by_id: dict[str, ConnectorProfile] = {}
|
|
||||||
for profile in [*database_profiles, *env_profiles]:
|
|
||||||
if profile.id not in profiles_by_id:
|
|
||||||
profiles_by_id[profile.id] = profile
|
|
||||||
profiles = list(profiles_by_id.values())
|
|
||||||
visible: list[ConnectorProfile] = []
|
|
||||||
for profile in profiles:
|
|
||||||
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 _connector_profile_visible(session, principal, profile, campaign_id=campaign_id, group_ids=group_ids):
|
|
||||||
continue
|
|
||||||
visible.append(_with_effective_connector_policy(session, principal, profile) if include_effective_policy else profile)
|
|
||||||
return visible
|
|
||||||
|
|
||||||
|
|
||||||
def _webdav_discovery_candidates(payload: FileConnectorDiscoveryRequest) -> list[str]:
|
def _webdav_discovery_candidates(payload: FileConnectorDiscoveryRequest) -> list[str]:
|
||||||
@@ -641,18 +614,6 @@ def _visible_connector_profile(
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connector profile not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Connector profile not found")
|
||||||
|
|
||||||
|
|
||||||
def _with_effective_connector_policy(session: Session, principal: ApiPrincipal, profile: ConnectorProfile) -> ConnectorProfile:
|
|
||||||
sources = effective_connector_policy_sources(
|
|
||||||
session,
|
|
||||||
tenant_id=principal.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]))
|
|
||||||
|
|
||||||
|
|
||||||
def _require_connector_profile_write(principal: ApiPrincipal, scope_type: str) -> None:
|
def _require_connector_profile_write(principal: ApiPrincipal, scope_type: str) -> None:
|
||||||
clean_scope = scope_type.strip().casefold()
|
clean_scope = scope_type.strip().casefold()
|
||||||
if clean_scope == "system":
|
if clean_scope == "system":
|
||||||
|
|||||||
180
src/govoplan_files/backend/storage/connector_visibility.py
Normal file
180
src/govoplan_files/backend/storage/connector_visibility.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
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 (
|
||||||
|
list_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 = list_database_connector_profiles(
|
||||||
|
session,
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
include_disabled=include_disabled,
|
||||||
|
)
|
||||||
|
configured_profiles = connector_profiles_from_settings(settings)
|
||||||
|
profiles_by_id: dict[str, ConnectorProfile] = {}
|
||||||
|
for profile in [*database_profiles, *configured_profiles]:
|
||||||
|
if 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."""
|
||||||
|
|
||||||
|
if (
|
||||||
|
not profile.enabled
|
||||||
|
or not profile.endpoint_url
|
||||||
|
or not profile.credentials_configured
|
||||||
|
):
|
||||||
|
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=connector_effective_endpoint_url(
|
||||||
|
provider=profile.provider,
|
||||||
|
endpoint_url=profile.endpoint_url,
|
||||||
|
metadata=profile.metadata,
|
||||||
|
),
|
||||||
|
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:
|
||||||
|
if profile.scope_type == "system":
|
||||||
|
return True
|
||||||
|
if profile.scope_type == "tenant":
|
||||||
|
return profile.scope_id == tenant_id
|
||||||
|
if profile.scope_type == "user":
|
||||||
|
return profile.scope_id == user_id
|
||||||
|
if profile.scope_type == "group":
|
||||||
|
return bool(profile.scope_id and profile.scope_id in group_ids)
|
||||||
|
if profile.scope_type != "campaign" or not profile.scope_id:
|
||||||
|
return False
|
||||||
|
if campaign_id and profile.scope_id != campaign_id:
|
||||||
|
return False
|
||||||
|
return bool(campaign_visible and campaign_visible(profile.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]))
|
||||||
240
tests/test_connector_visibility.py
Normal file
240
tests/test_connector_visibility.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from dataclasses import replace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from govoplan_files.backend.storage.connector_policy import ConnectorPolicySource
|
||||||
|
from govoplan_files.backend.storage.connector_profiles import ConnectorProfile
|
||||||
|
from govoplan_files.backend.storage.connector_visibility import (
|
||||||
|
connector_profile_usable_for_import,
|
||||||
|
visible_connector_profiles_for_actor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _profile(
|
||||||
|
profile_id: str,
|
||||||
|
*,
|
||||||
|
scope_type: str = "system",
|
||||||
|
scope_id: str | None = None,
|
||||||
|
provider: str = "webdav",
|
||||||
|
enabled: bool = True,
|
||||||
|
) -> ConnectorProfile:
|
||||||
|
return ConnectorProfile(
|
||||||
|
id=profile_id,
|
||||||
|
label=profile_id,
|
||||||
|
provider=provider,
|
||||||
|
scope_type=scope_type,
|
||||||
|
scope_id=scope_id,
|
||||||
|
endpoint_url=f"https://{profile_id}.example.invalid",
|
||||||
|
enabled=enabled,
|
||||||
|
credential_mode="anonymous",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectorVisibilityTests(unittest.TestCase):
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.effective_connector_policy_sources",
|
||||||
|
return_value=[],
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.connector_profiles_from_settings"
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.list_database_connector_profiles"
|
||||||
|
)
|
||||||
|
def test_profiles_are_filtered_to_actor_scopes_and_database_definition_wins(
|
||||||
|
self,
|
||||||
|
database_profiles,
|
||||||
|
configured_profiles,
|
||||||
|
_policy_sources,
|
||||||
|
) -> None:
|
||||||
|
database_profiles.return_value = [
|
||||||
|
_profile("system"),
|
||||||
|
_profile("tenant", scope_type="tenant", scope_id="tenant-1"),
|
||||||
|
_profile("other-tenant", scope_type="tenant", scope_id="tenant-2"),
|
||||||
|
_profile("user", scope_type="user", scope_id="user-1"),
|
||||||
|
_profile("other-user", scope_type="user", scope_id="user-2"),
|
||||||
|
_profile("group", scope_type="group", scope_id="group-1"),
|
||||||
|
_profile("campaign", scope_type="campaign", scope_id="campaign-1"),
|
||||||
|
_profile("disabled", enabled=False),
|
||||||
|
_profile("duplicate", provider="webdav"),
|
||||||
|
]
|
||||||
|
configured_profiles.return_value = [_profile("duplicate", provider="nextcloud")]
|
||||||
|
|
||||||
|
visible = visible_connector_profiles_for_actor(
|
||||||
|
object(), # type: ignore[arg-type]
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
user_id="user-1",
|
||||||
|
group_ids={"group-1"},
|
||||||
|
settings=object(),
|
||||||
|
campaign_visible=lambda campaign_id: campaign_id == "campaign-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
{"system", "tenant", "user", "group", "campaign", "duplicate"},
|
||||||
|
{profile.id for profile in visible},
|
||||||
|
)
|
||||||
|
duplicate = next(profile for profile in visible if profile.id == "duplicate")
|
||||||
|
self.assertEqual("webdav", duplicate.provider)
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.effective_connector_policy_sources",
|
||||||
|
return_value=[],
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.connector_profiles_from_settings",
|
||||||
|
return_value=[],
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.list_database_connector_profiles"
|
||||||
|
)
|
||||||
|
def test_campaign_and_admin_visibility_remain_explicit(
|
||||||
|
self,
|
||||||
|
database_profiles,
|
||||||
|
_configured_profiles,
|
||||||
|
_policy_sources,
|
||||||
|
) -> None:
|
||||||
|
database_profiles.return_value = [
|
||||||
|
_profile("campaign-a", scope_type="campaign", scope_id="campaign-a"),
|
||||||
|
_profile("campaign-b", scope_type="campaign", scope_id="campaign-b"),
|
||||||
|
_profile("other-user", scope_type="user", scope_id="user-2"),
|
||||||
|
]
|
||||||
|
|
||||||
|
campaign_only = visible_connector_profiles_for_actor(
|
||||||
|
object(), # type: ignore[arg-type]
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
user_id="user-1",
|
||||||
|
group_ids=(),
|
||||||
|
settings=None,
|
||||||
|
campaign_id="campaign-a",
|
||||||
|
campaign_visible=lambda _campaign_id: True,
|
||||||
|
)
|
||||||
|
self.assertEqual(["campaign-a"], [profile.id for profile in campaign_only])
|
||||||
|
|
||||||
|
admin = visible_connector_profiles_for_actor(
|
||||||
|
object(), # type: ignore[arg-type]
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
user_id="user-1",
|
||||||
|
group_ids=(),
|
||||||
|
settings=None,
|
||||||
|
campaign_visible=None,
|
||||||
|
include_admin_scopes=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
{"campaign-a", "campaign-b", "other-user"},
|
||||||
|
{profile.id for profile in admin},
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.connector_profiles_from_settings",
|
||||||
|
return_value=[],
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.list_database_connector_profiles"
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"govoplan_files.backend.storage.connector_visibility.effective_connector_policy_sources"
|
||||||
|
)
|
||||||
|
def test_effective_policy_is_attached_without_mutating_profile(
|
||||||
|
self,
|
||||||
|
policy_sources,
|
||||||
|
database_profiles,
|
||||||
|
_configured_profiles,
|
||||||
|
) -> None:
|
||||||
|
profile = _profile("profile")
|
||||||
|
source = ConnectorPolicySource(
|
||||||
|
scope_type="system",
|
||||||
|
label="System",
|
||||||
|
policy={"allow": {"providers": ["webdav"]}},
|
||||||
|
)
|
||||||
|
database_profiles.return_value = [profile]
|
||||||
|
policy_sources.return_value = [source]
|
||||||
|
|
||||||
|
visible = visible_connector_profiles_for_actor(
|
||||||
|
object(), # type: ignore[arg-type]
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
user_id="user-1",
|
||||||
|
group_ids=(),
|
||||||
|
settings=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual((), profile.policy_sources)
|
||||||
|
self.assertEqual((source,), visible[0].policy_sources)
|
||||||
|
|
||||||
|
def test_import_usability_requires_safe_provider_credentials_endpoint_and_identity_policy(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
self.assertTrue(connector_profile_usable_for_import(_profile("webdav")))
|
||||||
|
self.assertFalse(
|
||||||
|
connector_profile_usable_for_import(_profile("s3", provider="s3"))
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
connector_profile_usable_for_import(_profile("smb", provider="smb"))
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
connector_profile_usable_for_import(
|
||||||
|
_profile("sharepoint", provider="sharepoint")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
connector_profile_usable_for_import(
|
||||||
|
ConnectorProfile(
|
||||||
|
id="no-endpoint",
|
||||||
|
label="No endpoint",
|
||||||
|
provider="webdav",
|
||||||
|
credential_mode="anonymous",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
connector_profile_usable_for_import(
|
||||||
|
ConnectorProfile(
|
||||||
|
id="no-credentials",
|
||||||
|
label="No credentials",
|
||||||
|
provider="webdav",
|
||||||
|
endpoint_url="https://files.example.invalid",
|
||||||
|
credential_mode="basic",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
denied = replace(
|
||||||
|
_profile("denied"),
|
||||||
|
policy_sources=(
|
||||||
|
ConnectorPolicySource(
|
||||||
|
scope_type="system",
|
||||||
|
label="System",
|
||||||
|
policy={"deny": {"providers": ["webdav"]}},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertFalse(connector_profile_usable_for_import(denied))
|
||||||
|
|
||||||
|
path_limited = replace(
|
||||||
|
_profile("path-limited"),
|
||||||
|
policy_sources=(
|
||||||
|
ConnectorPolicySource(
|
||||||
|
scope_type="system",
|
||||||
|
label="System",
|
||||||
|
policy={"allow": {"external_paths": ["/approved/*"]}},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertFalse(connector_profile_usable_for_import(path_limited))
|
||||||
|
|
||||||
|
endpoint_allowed = replace(
|
||||||
|
_profile("endpoint-allowed"),
|
||||||
|
policy_sources=(
|
||||||
|
ConnectorPolicySource(
|
||||||
|
scope_type="system",
|
||||||
|
label="System",
|
||||||
|
policy={"allow": {"external_urls": ["https://*.example.invalid"]}},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertTrue(connector_profile_usable_for_import(endpoint_allowed))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user