diff --git a/modules/chat/handling/promptFactory.py b/modules/chat/handling/promptFactory.py index ba5f6dce..32f35cc4 100644 --- a/modules/chat/handling/promptFactory.py +++ b/modules/chat/handling/promptFactory.py @@ -89,6 +89,11 @@ async def createActionDefinitionPrompt(context, service) -> str: docRefs = service.getDocumentReferenceList() connRefs = service.getConnectionReferenceList() + # Debug logging for connections + logging.debug(f"Connection references retrieved: {connRefs}") + logging.debug(f"Connection references type: {type(connRefs)}") + logging.debug(f"Connection references length: {len(connRefs) if connRefs else 0}") + # Get documents from current round (chat) and entire workflow history current_round_docs = docRefs.get('chat', []) workflow_history_docs = docRefs.get('history', []) diff --git a/modules/chat/serviceCenter.py b/modules/chat/serviceCenter.py index a703d5bd..8ea6d000 100644 --- a/modules/chat/serviceCenter.py +++ b/modules/chat/serviceCenter.py @@ -443,12 +443,23 @@ class ServiceCenter: """Get list of all UserConnection objects as references with enhanced state information""" connections = [] # Get user connections through AppObjects interface + logger.debug(f"getConnectionReferenceList: Service center user ID: {self.user.id}") + logger.debug(f"getConnectionReferenceList: Service center user type: {type(self.user)}") + logger.debug(f"getConnectionReferenceList: Service center user object: {self.user}") + user_connections = self.interfaceApp.getUserConnections(self.user.id) + logger.debug(f"getConnectionReferenceList: User ID: {self.user.id}") + logger.debug(f"getConnectionReferenceList: Raw user connections: {user_connections}") + logger.debug(f"getConnectionReferenceList: User connections type: {type(user_connections)}") + logger.debug(f"getConnectionReferenceList: User connections length: {len(user_connections) if user_connections else 0}") + for conn in user_connections: # Get enhanced connection reference with state information enhanced_ref = self.getConnectionReferenceFromUserConnection(conn) + logger.debug(f"getConnectionReferenceList: Enhanced ref for connection {conn.id}: {enhanced_ref}") connections.append(enhanced_ref) # Sort by connection reference + logger.debug(f"getConnectionReferenceList: Final connections list: {connections}") return sorted(connections) def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str: @@ -461,6 +472,8 @@ class ServiceCenter: if token: if hasattr(token, 'expiresAt') and token.expiresAt: current_time = get_utc_timestamp() + logger.debug(f"getConnectionReferenceFromUserConnection: Current time: {current_time}") + logger.debug(f"getConnectionReferenceFromUserConnection: Token expires at: {token.expiresAt}") if current_time > token.expiresAt: token_status = "expired" else: @@ -476,6 +489,7 @@ class ServiceCenter: base_ref = f"connection:{connection.authority.value}:{connection.externalUsername}:{connection.id}" state_info = f" [status:{connection.status.value}, token:{token_status}]" + logger.debug(f"getConnectionReferenceFromUserConnection: Built reference: {base_ref + state_info}") return base_ref + state_info def getUserConnectionFromConnectionReference(self, connectionReference: str) -> Optional[UserConnection]: diff --git a/modules/interfaces/interfaceAppObjects.py b/modules/interfaces/interfaceAppObjects.py index a7cc91ed..587d5b6b 100644 --- a/modules/interfaces/interfaceAppObjects.py +++ b/modules/interfaces/interfaceAppObjects.py @@ -277,13 +277,21 @@ class AppObjects: def getUserConnections(self, userId: str) -> List[UserConnection]: """Returns all connections for a user.""" try: + logger.debug(f"getUserConnections: Looking for connections for user ID: {userId}") + logger.debug(f"getUserConnections: Current database context userId: {self.db.userId}") + logger.debug(f"getUserConnections: Current interface userId: {self.userId}") + # Get connections for this user connections = self.db.getRecordset("connections", recordFilter={"userId": userId}) + logger.debug(f"getUserConnections: Raw database connections: {connections}") + logger.debug(f"getUserConnections: Database connections type: {type(connections)}") + logger.debug(f"getUserConnections: Database connections length: {len(connections) if connections else 0}") # Convert to UserConnection objects result = [] for conn_dict in connections: try: + logger.debug(f"getUserConnections: Processing connection dict: {conn_dict}") # Create UserConnection object connection = UserConnection( id=conn_dict["id"], @@ -297,11 +305,13 @@ class AppObjects: lastChecked=conn_dict.get("lastChecked"), expiresAt=conn_dict.get("expiresAt") ) + logger.debug(f"getUserConnections: Created UserConnection object: {connection}") result.append(connection) except Exception as e: logger.error(f"Error converting connection dict to object: {str(e)}") continue + logger.debug(f"getUserConnections: Final result: {result}") return result except Exception as e: diff --git a/modules/routes/routeDataConnections.py b/modules/routes/routeDataConnections.py index f66fb6cc..c108480e 100644 --- a/modules/routes/routeDataConnections.py +++ b/modules/routes/routeDataConnections.py @@ -1,6 +1,11 @@ """ Connection routes for the backend API. Implements the endpoints for connection management. + +SECURITY NOTE: +- Regular connections endpoint (/api/connections/) only returns connections for the current user +- Admin endpoint (/api/connections/admin/all) provides access to all connections for management purposes +- This prevents security vulnerabilities where admin users could see other users' connections """ from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request, Response @@ -30,23 +35,20 @@ async def get_connections( request: Request, currentUser: User = Depends(getCurrentUser) ) -> List[UserConnection]: - """Get all connections for the current user or all connections if admin""" + """Get all connections for the current user + + SECURITY: This endpoint is secure - users can only see their own connections. + """ try: interface = getInterface(currentUser) # Clear connections cache to ensure fresh data interface.db.clearTableCache("connections") - if currentUser.privilege in ['admin', 'sysadmin']: - # Admins can see all connections - users = interface.getAllUsers() - connections = [] - for user in users: - connections.extend(interface.getUserConnections(user.id)) - return connections - else: - # Regular users can only see their own connections - return interface.getUserConnections(currentUser.id) + # SECURITY FIX: All users (including admins) can only see their own connections + # This prevents admin from seeing other users' connections and causing confusion + return interface.getUserConnections(currentUser.id) + except Exception as e: logger.error(f"Error getting connections: {str(e)}") raise HTTPException( @@ -61,6 +63,11 @@ async def create_connection( connection_data: Dict[str, Any] = Body(...), currentUser: User = Depends(getCurrentUser) ) -> UserConnection: + """Create a new connection for the current user + + SECURITY: This endpoint is secure - it always creates connections for the current user + and cannot be used to create connections for other users. + """ try: interface = getInterface(currentUser) @@ -120,30 +127,22 @@ async def update_connection( connection_data: Dict[str, Any] = Body(...), currentUser: User = Depends(getCurrentUser) ) -> UserConnection: - """Update an existing connection""" + """Update an existing connection for the current user + + SECURITY: This endpoint is secure - users can only update their own connections. + """ try: interface = getInterface(currentUser) # Find the connection connection = None - if currentUser.privilege in ['admin', 'sysadmin']: - # Admins can update any connection - users = interface.getAllUsers() - for user in users: - connections = interface.getUserConnections(user.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break - if connection: - break - else: - # Regular users can only update their own connections - connections = interface.getUserConnections(currentUser.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break + # SECURITY FIX: All users (including admins) can only update their own connections + # This prevents admin from updating other users' connections and causing confusion + connections = interface.getUserConnections(currentUser.id) + for conn in connections: + if conn.id == connectionId: + connection = conn + break if not connection: raise HTTPException( @@ -184,30 +183,23 @@ async def connect_service( connectionId: str = Path(..., description="The ID of the connection to connect"), currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: + """Connect a service for the current user + + SECURITY: This endpoint is secure - users can only connect their own connections. + """ try: interface = getInterface(currentUser) # Find the connection connection = None - if currentUser.privilege in ['admin', 'sysadmin']: - # Admins can connect any connection - users = interface.getAllUsers() - for user in users: - connections = interface.getUserConnections(user.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break - if connection: - break - else: - # Regular users can only connect their own connections - connections = interface.getUserConnections(currentUser.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break + # SECURITY FIX: All users (including admins) can only connect their own connections + # This prevents admin from connecting other users' connections and causing confusion + connections = interface.getUserConnections(currentUser.id) + for conn in connections: + if conn.id == connectionId: + connection = conn + break if not connection: raise HTTPException( @@ -257,30 +249,23 @@ async def disconnect_service( connectionId: str = Path(..., description="The ID of the connection to disconnect"), currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: + """Disconnect a service for the current user + + SECURITY: This endpoint is secure - users can only disconnect their own connections. + """ try: interface = getInterface(currentUser) # Find the connection connection = None - if currentUser.privilege in ['admin', 'sysadmin']: - # Admins can disconnect any connection - users = interface.getAllUsers() - for user in users: - connections = interface.getUserConnections(user.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break - if connection: - break - else: - # Regular users can only disconnect their own connections - connections = interface.getUserConnections(currentUser.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break + # SECURITY FIX: All users (including admins) can only disconnect their own connections + # This prevents admin from disconnecting other users' connections and causing confusion + connections = interface.getUserConnections(currentUser.id) + for conn in connections: + if conn.id == connectionId: + connection = conn + break if not connection: raise HTTPException( @@ -316,30 +301,23 @@ async def delete_connection( connectionId: str = Path(..., description="The ID of the connection to delete"), currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: + """Delete a connection for the current user + + SECURITY: This endpoint is secure - users can only delete their own connections. + """ try: interface = getInterface(currentUser) # Find the connection connection = None - if currentUser.privilege in ['admin', 'sysadmin']: - # Admins can delete any connection - users = interface.getAllUsers() - for user in users: - connections = interface.getUserConnections(user.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break - if connection: - break - else: - # Regular users can only delete their own connections - connections = interface.getUserConnections(currentUser.id) - for conn in connections: - if conn.id == connectionId: - connection = conn - break + # SECURITY FIX: All users (including admins) can only delete their own connections + # This prevents admin from deleting other users' connections and causing confusion + connections = interface.getUserConnections(currentUser.id) + for conn in connections: + if conn.id == connectionId: + connection = conn + break if not connection: raise HTTPException(