""" Tests to verify session["username"] hardening across all route files. All @login_required route handlers should use session["username"] (direct access) instead of session.get("username"). The @login_required decorator guarantees the key exists; direct access fails fast if the decorator is ever removed. """ import inspect import textwrap def _get_function_source(func): """Get dedented source of a function for AST analysis.""" return textwrap.dedent(inspect.getsource(func)) def _uses_unsafe_session_get(source: str) -> bool: """Check if source contains session.get("username") or flask_session.get("username").""" return ( 'session.get("username")' in source or "session.get('username')" in source ) def _uses_safe_session_access(source: str) -> bool: """Check if source contains session["username"] or flask_session["username"].""" return 'session["username"]' in source or "session['username']" in source class TestNewsRoutesSessionAccess: """Verify news routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.web.routes.news_routes import ( create_subscription, get_news_feed, get_subscriptions, save_preferences, submit_feedback, ) return [ get_news_feed, get_subscriptions, create_subscription, submit_feedback, save_preferences, ] def test_no_session_get_username(self): """No news route should use session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username') " f"instead of session['username']" ) def test_uses_session_bracket_access(self): """Each news route that accesses username should use bracket notation.""" for func in self._get_route_functions(): source = _get_function_source(func) assert _uses_safe_session_access(source), ( f"{func.__name__} should use session['username']" ) class TestHistoryRoutesSessionAccess: """Verify history routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.web.routes.history_routes import ( get_history, get_research_details, get_research_logs, get_research_status, ) return [ get_history, get_research_status, get_research_details, get_research_logs, ] def test_no_session_get_username(self): for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) class TestResearchRoutesSessionAccess: """Verify research routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.web.routes.research_routes import ( clear_history, delete_research, export_research_report, get_history, get_queue_position, get_queue_status, get_research_details, get_research_logs, get_research_report, get_research_status, start_research, terminate_research, upload_pdf, ) return [ start_research, terminate_research, delete_research, clear_history, get_history, get_research_details, get_research_logs, get_research_report, export_research_report, get_research_status, get_queue_status, get_queue_position, upload_pdf, ] def test_no_session_get_username(self): for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) class TestContextOverflowSessionAccess: """Verify context overflow routes use flask_session['username'].""" def test_no_session_get_username(self): from local_deep_research.web.routes.context_overflow_api import ( get_context_overflow_metrics, ) source = _get_function_source(get_context_overflow_metrics) assert not _uses_unsafe_session_get(source), ( "get_context_overflow_metrics uses session.get('username')" ) class TestApiRoutesSessionAccess: """Verify API routes use session['username'].""" def test_no_session_get_username(self): from local_deep_research.web.routes.api_routes import ( api_terminate_research, ) source = _get_function_source(api_terminate_research) assert not _uses_unsafe_session_get(source), ( "api_terminate_research uses session.get('username')" ) class TestSettingsRoutesSessionAccess: """Verify settings routes use session['username'] (except helper functions).""" def _get_route_functions(self): from local_deep_research.web.routes.settings_routes import ( api_delete_setting, api_get_all_settings, api_get_available_models, api_get_available_search_engines, api_get_categories, api_get_db_setting, api_get_search_favorites, api_import_settings, api_toggle_search_favorite, api_update_search_favorites, api_update_setting, fix_corrupted_settings, reset_to_defaults, save_all_settings, save_settings, ) return [ save_all_settings, reset_to_defaults, save_settings, api_get_all_settings, api_get_db_setting, api_update_setting, api_delete_setting, api_import_settings, api_get_categories, api_get_available_models, api_get_available_search_engines, api_get_search_favorites, api_update_search_favorites, api_toggle_search_favorite, fix_corrupted_settings, ] def test_no_session_get_username_in_routes(self): """Decorated route functions should not use session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) def test_helper_functions_use_safe_get(self): """Helper functions (not decorated) should still use session.get('username').""" from local_deep_research.web.routes.settings_routes import ( _get_setting_from_session, ) from local_deep_research.web.warning_checks import calculate_warnings for func in [_get_setting_from_session, calculate_warnings]: source = _get_function_source(func) assert _uses_unsafe_session_get(source), ( f"{func.__name__} should use session.get('username') " f"because it is not behind @login_required" ) class TestLibraryRoutesSessionAccess: """Verify library routes use session['username'].""" def _get_route_functions(self): from local_deep_research.research_library.routes.library_routes import ( check_downloads, delete_document, document_details_page, download_all_text, download_bulk, download_research_pdfs, download_single_resource, download_source, download_text_single, get_collections_list, get_documents, get_library_stats, get_research_list, get_research_sources, library_page, mark_for_redownload, download_manager_page, queue_all_undownloaded, serve_text_api, sync_library, toggle_favorite, view_pdf_page, view_text_page, ) return [ library_page, document_details_page, download_manager_page, get_library_stats, get_collections_list, get_documents, toggle_favorite, delete_document, view_pdf_page, view_text_page, serve_text_api, download_single_resource, download_text_single, download_all_text, download_research_pdfs, download_bulk, get_research_list, sync_library, mark_for_redownload, queue_all_undownloaded, get_research_sources, check_downloads, download_source, ] def test_no_session_get_username(self): for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) class TestFollowupRoutesSessionAccess: """Verify followup routes use session['username'].""" def test_no_session_get_username(self): from local_deep_research.followup_research.routes import ( prepare_followup, start_followup, ) for func in [prepare_followup, start_followup]: source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) class TestSchedulerRoutesSessionAccess: """Verify scheduler helper uses session.get('username') (not decorated).""" def test_helper_uses_safe_get(self): """get_current_username is a helper, not a route — it should use .get().""" from local_deep_research.research_scheduler.routes import ( get_current_username, ) source = _get_function_source(get_current_username) assert _uses_unsafe_session_get(source), ( "get_current_username should use session.get('username') " "because it is not behind @login_required" ) class TestBenchmarkRoutesSessionAccess: """Verify benchmark routes use flask_session['username'].""" def _get_route_functions(self): from local_deep_research.benchmarks.web_api.benchmark_routes import ( cancel_benchmark, delete_benchmark_run, export_benchmark_results, get_benchmark_history, get_benchmark_results, get_benchmark_status, get_running_benchmark, index, start_benchmark, start_benchmark_simple, ) return [ index, start_benchmark, get_running_benchmark, get_benchmark_status, cancel_benchmark, get_benchmark_history, get_benchmark_results, start_benchmark_simple, delete_benchmark_run, export_benchmark_results, ] def test_no_session_get_username(self): for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) class TestMetricsRoutesSessionAccess: """Verify metrics routes use flask_session['username'] (except helper functions).""" def _get_route_functions(self): from local_deep_research.web.routes.metrics_routes import ( api_classify_domains, api_classification_progress, api_cost_analytics, api_enhanced_metrics, api_get_classifications_summary, api_get_domain_classifications, api_get_research_rating, api_link_analytics, api_metrics, api_rate_limiting_metrics, api_research_costs, api_research_link_metrics, api_save_research_rating, api_star_reviews, ) return [ api_metrics, api_rate_limiting_metrics, api_research_link_metrics, api_enhanced_metrics, api_get_research_rating, api_save_research_rating, api_star_reviews, api_research_costs, api_cost_analytics, api_link_analytics, api_get_domain_classifications, api_get_classifications_summary, api_classify_domains, api_classification_progress, ] def test_no_session_get_username_in_routes(self): """Decorated route functions should not use flask_session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username')" ) def test_helper_functions_use_safe_get(self): """Helper functions (not decorated) should still use flask_session.get('username').""" from local_deep_research.web.routes.metrics_routes import ( get_link_analytics, get_rate_limiting_analytics, get_rating_analytics, get_strategy_analytics, ) for func in [ get_rating_analytics, get_link_analytics, get_strategy_analytics, get_rate_limiting_analytics, ]: source = _get_function_source(func) assert _uses_unsafe_session_get(source), ( f"{func.__name__} should use flask_session.get('username') " f"because it is not behind @login_required" ) class TestRAGRoutesSessionAccess: """Verify RAG routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.research_library.routes.rag_routes import ( cancel_indexing, configure_rag, create_collection, get_collection_documents, get_collections, get_documents, get_index_info, get_index_status, get_rag_stats, index_all, index_collection, index_document, remove_document, start_background_index, update_collection, upload_to_collection, view_document_chunks, ) return [ view_document_chunks, get_index_info, get_rag_stats, index_document, remove_document, index_all, configure_rag, get_documents, get_collections, create_collection, update_collection, upload_to_collection, get_collection_documents, index_collection, start_background_index, get_index_status, cancel_indexing, ] def test_no_session_get_username(self): """No RAG route should use session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username') " f"instead of session['username']" ) class TestDeleteRoutesSessionAccess: """Verify delete routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.research_library.deletion.routes.delete_routes import ( delete_collection, delete_collection_index, delete_document, delete_document_blob, delete_documents_blobs_bulk, delete_documents_bulk, get_bulk_deletion_preview, get_collection_deletion_preview, get_document_deletion_preview, remove_document_from_collection, remove_documents_from_collection_bulk, ) return [ delete_document, delete_document_blob, get_document_deletion_preview, remove_document_from_collection, delete_collection, delete_collection_index, get_collection_deletion_preview, delete_documents_bulk, delete_documents_blobs_bulk, remove_documents_from_collection_bulk, get_bulk_deletion_preview, ] def test_no_session_get_username(self): """No delete route should use session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username') " f"instead of session['username']" ) class TestNewsFlaskApiSessionAccess: """Verify news flask_api routes use session['username'] not session.get('username').""" def _get_route_functions(self): from local_deep_research.news.flask_api import ( check_overdue_subscriptions, run_subscription_now, scheduler_stats, ) return [ run_subscription_now, scheduler_stats, check_overdue_subscriptions, ] def test_no_session_get_username(self): """No news flask_api route should use session.get('username').""" for func in self._get_route_functions(): source = _get_function_source(func) assert not _uses_unsafe_session_get(source), ( f"{func.__name__} uses session.get('username') " f"instead of session['username']" )