diff --git a/surfsense_backend/app/routes/composio_routes.py b/surfsense_backend/app/routes/composio_routes.py index 1edbb0036..410f90256 100644 --- a/surfsense_backend/app/routes/composio_routes.py +++ b/surfsense_backend/app/routes/composio_routes.py @@ -27,7 +27,6 @@ from app.config import config from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) from app.services.composio_service import ( @@ -36,12 +35,13 @@ from app.services.composio_service import ( TOOLKIT_TO_CONNECTOR_TYPE, ComposioService, ) -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( count_connectors_of_type, get_base_name_for_type, ) from app.utils.oauth_security import OAuthStateManager +from app.utils.rbac import check_search_space_access logger = logging.getLogger(__name__) @@ -69,13 +69,16 @@ def get_state_manager() -> OAuthStateManager: @router.get("/composio/toolkits") -async def list_composio_toolkits(user: User = Depends(current_active_user)): +async def list_composio_toolkits( + auth: AuthContext = Depends(require_session_context), +): """ List available Composio toolkits. Returns: JSON with list of available toolkits and their metadata. """ + del auth if not ComposioService.is_enabled(): raise HTTPException( status_code=503, @@ -647,7 +650,7 @@ async def list_composio_drive_folders( connector_id: int, parent_id: str | None = None, session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ): """ List folders AND files in user's Google Drive via Composio. @@ -662,6 +665,7 @@ async def list_composio_drive_folders( ) connector = None + user = auth.user try: result = await session.execute( select(SearchSourceConnector).filter( @@ -679,6 +683,8 @@ async def list_composio_drive_folders( detail="Composio Google Drive connector not found or access denied", ) + await check_search_space_access(session, auth, connector.search_space_id) + composio_connected_account_id = connector.config.get( "composio_connected_account_id" ) diff --git a/surfsense_backend/app/routes/discord_add_connector_route.py b/surfsense_backend/app/routes/discord_add_connector_route.py index ab2f9cfbb..1da0987b0 100644 --- a/surfsense_backend/app/routes/discord_add_connector_route.py +++ b/surfsense_backend/app/routes/discord_add_connector_route.py @@ -20,17 +20,17 @@ from app.config import config from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) from app.schemas.discord_auth_credentials import DiscordAuthCredentialsBase -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) from app.utils.oauth_security import OAuthStateManager, TokenEncryption +from app.utils.rbac import check_search_space_access logger = logging.getLogger(__name__) @@ -615,7 +615,7 @@ def _compute_channel_permissions( async def get_discord_channels( connector_id: int, session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ): """ Get list of Discord text channels for a connector with permission info. @@ -633,6 +633,7 @@ async def get_discord_channels( """ from sqlalchemy import select + user = auth.user try: # Get connector and verify ownership result = await session.execute( @@ -651,6 +652,8 @@ async def get_discord_channels( detail="Discord connector not found or access denied", ) + await check_search_space_access(session, auth, connector.search_space_id) + # Get credentials and decrypt bot token credentials = DiscordAuthCredentialsBase.from_dict(connector.config) token_encryption = get_token_encryption() diff --git a/surfsense_backend/app/routes/dropbox_add_connector_route.py b/surfsense_backend/app/routes/dropbox_add_connector_route.py index d35b7f38c..6a9284371 100644 --- a/surfsense_backend/app/routes/dropbox_add_connector_route.py +++ b/surfsense_backend/app/routes/dropbox_add_connector_route.py @@ -27,16 +27,16 @@ from app.connectors.dropbox import DropboxClient, list_folder_contents from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) from app.utils.oauth_security import OAuthStateManager, TokenEncryption +from app.utils.rbac import check_search_space_access logger = logging.getLogger(__name__) router = APIRouter() @@ -411,10 +411,11 @@ async def list_dropbox_folders( connector_id: int, parent_path: str = "", session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ): """List folders and files in user's Dropbox.""" connector = None + user = auth.user try: result = await session.execute( select(SearchSourceConnector).filter( @@ -430,6 +431,8 @@ async def list_dropbox_folders( status_code=404, detail="Dropbox connector not found or access denied" ) + await check_search_space_access(session, auth, connector.search_space_id) + dropbox_client = DropboxClient(session, connector_id) items, error = await list_folder_contents(dropbox_client, path=parent_path) diff --git a/surfsense_backend/app/routes/google_drive_add_connector_route.py b/surfsense_backend/app/routes/google_drive_add_connector_route.py index db4bf7ef3..c97c82eb0 100644 --- a/surfsense_backend/app/routes/google_drive_add_connector_route.py +++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py @@ -34,10 +34,9 @@ from app.connectors.google_gmail_connector import fetch_google_user_email from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( check_duplicate_connector, generate_unique_connector_name, @@ -47,6 +46,7 @@ from app.utils.oauth_security import ( TokenEncryption, generate_code_verifier, ) +from app.utils.rbac import check_search_space_access # Relax token scope validation for Google OAuth os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1" @@ -478,7 +478,7 @@ async def list_google_drive_folders( connector_id: int, parent_id: str | None = None, session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ): """ List folders AND files in user's Google Drive with hierarchical support. @@ -498,6 +498,7 @@ async def list_google_drive_folders( ] } """ + user = auth.user try: # Get connector and verify ownership result = await session.execute( @@ -516,6 +517,8 @@ async def list_google_drive_folders( detail="Google Drive connector not found or access denied", ) + await check_search_space_access(session, auth, connector.search_space_id) + # Initialize Drive client (credentials will be loaded on first API call) drive_client = GoogleDriveClient(session, connector_id) diff --git a/surfsense_backend/app/routes/onedrive_add_connector_route.py b/surfsense_backend/app/routes/onedrive_add_connector_route.py index 3b14fa2f8..9c55d4fe7 100644 --- a/surfsense_backend/app/routes/onedrive_add_connector_route.py +++ b/surfsense_backend/app/routes/onedrive_add_connector_route.py @@ -27,16 +27,16 @@ from app.connectors.onedrive import OneDriveClient, list_folder_contents from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) from app.utils.oauth_security import OAuthStateManager, TokenEncryption +from app.utils.rbac import check_search_space_access logger = logging.getLogger(__name__) router = APIRouter() @@ -418,10 +418,11 @@ async def list_onedrive_folders( connector_id: int, parent_id: str | None = None, session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ): """List folders and files in user's OneDrive.""" connector = None + user = auth.user try: result = await session.execute( select(SearchSourceConnector).filter( @@ -437,6 +438,8 @@ async def list_onedrive_folders( status_code=404, detail="OneDrive connector not found or access denied" ) + await check_search_space_access(session, auth, connector.search_space_id) + onedrive_client = OneDriveClient(session, connector_id) items, error = await list_folder_contents(onedrive_client, parent_id=parent_id) diff --git a/surfsense_backend/app/routes/slack_add_connector_route.py b/surfsense_backend/app/routes/slack_add_connector_route.py index ca5d0c9a4..ee6f75417 100644 --- a/surfsense_backend/app/routes/slack_add_connector_route.py +++ b/surfsense_backend/app/routes/slack_add_connector_route.py @@ -22,17 +22,17 @@ from app.config import config from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - User, get_async_session, ) from app.schemas.slack_auth_credentials import SlackAuthCredentialsBase -from app.users import current_active_user, require_session_context +from app.users import get_auth_context, require_session_context from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) from app.utils.oauth_security import OAuthStateManager, TokenEncryption +from app.utils.rbac import check_search_space_access logger = logging.getLogger(__name__) @@ -530,7 +530,7 @@ async def refresh_slack_token( async def get_slack_channels( connector_id: int, session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), + auth: AuthContext = Depends(get_auth_context), ) -> list[dict[str, Any]]: """ Get list of Slack channels with bot membership status. @@ -546,6 +546,7 @@ async def get_slack_channels( Returns: List of channels with id, name, is_private, and is_member fields """ + user = auth.user try: # Get the connector and verify ownership result = await session.execute( @@ -564,6 +565,8 @@ async def get_slack_channels( detail="Slack connector not found or access denied", ) + await check_search_space_access(session, auth, connector.search_space_id) + # Get credentials and decrypt bot token credentials = SlackAuthCredentialsBase.from_dict(connector.config) token_encryption = get_token_encryption()