databricks-tellr-app 0.3.13.dev4__tar.gz → 0.3.13.dev12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/PKG-INFO +8 -8
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app/run.py +0 -12
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/PKG-INFO +8 -8
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/SOURCES.txt +2 -2
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/requires.txt +7 -7
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/pyproject.toml +8 -10
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/main.py +25 -1
- databricks_tellr_app-0.3.13.dev12/src/api/routes/_authz.py +337 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin.py +5 -1
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin_usage.py +9 -2
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/agent_config.py +8 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/chat.py +13 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/export.py +87 -67
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/feedback.py +4 -3
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/google_slides.py +28 -6
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/images.py +36 -7
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/profiles.py +6 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/sessions.py +5 -100
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/deck_prompts.py +33 -21
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/slide_styles.py +35 -22
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/slides.py +67 -109
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/tour.py +4 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/verification.py +13 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/databricks_client.py +33 -6
- databricks_tellr_app-0.3.13.dev12/src/core/encryption.py +86 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/lakebase.py +114 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/__init__.py +0 -2
- databricks_tellr_app-0.3.13.dev4/src/core/encryption.py +0 -125
- databricks_tellr_app-0.3.13.dev4/src/database/models/encryption_key.py +0 -30
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/README.md +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/setup.cfg +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/export_job_queue.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/feedback_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/session_manager.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/usage_events.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/usage_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/database.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/usage_event.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/agent_factory.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/google_slides_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/html_to_google_slides.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/html_to_pptx.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/spotlight.py +0 -0
- {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/text_caps.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: databricks-tellr-app
|
|
3
|
-
Version: 0.3.13.
|
|
3
|
+
Version: 0.3.13.dev12
|
|
4
4
|
Summary: Tellr application package for Databricks Apps
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -24,7 +24,7 @@ Requires-Dist: tinycss2==1.3.0
|
|
|
24
24
|
Requires-Dist: esprima==4.0.1
|
|
25
25
|
Requires-Dist: sqlalchemy==2.0.36
|
|
26
26
|
Requires-Dist: psycopg2-binary==2.9.10
|
|
27
|
-
Requires-Dist: databricks-sql-connector
|
|
27
|
+
Requires-Dist: databricks-sql-connector>=3.0.0
|
|
28
28
|
Requires-Dist: httpx==0.28.1
|
|
29
29
|
Requires-Dist: jinja2==3.1.2
|
|
30
30
|
Requires-Dist: python-dotenv==1.2.1
|
|
@@ -32,12 +32,12 @@ Requires-Dist: pyyaml==6.0.1
|
|
|
32
32
|
Requires-Dist: pandas==2.3.3
|
|
33
33
|
Requires-Dist: python-pptx==0.6.23
|
|
34
34
|
Requires-Dist: playwright==1.48.0
|
|
35
|
-
Requires-Dist: Pillow
|
|
36
|
-
Requires-Dist: google-api-python-client
|
|
37
|
-
Requires-Dist: google-auth-oauthlib
|
|
38
|
-
Requires-Dist: google-auth-httplib2
|
|
39
|
-
Requires-Dist: cryptography
|
|
40
|
-
Requires-Dist: sse-starlette
|
|
35
|
+
Requires-Dist: Pillow>=10.0.0
|
|
36
|
+
Requires-Dist: google-api-python-client>=2.100.0
|
|
37
|
+
Requires-Dist: google-auth-oauthlib>=1.2.0
|
|
38
|
+
Requires-Dist: google-auth-httplib2>=0.2.0
|
|
39
|
+
Requires-Dist: cryptography>=42.0.0
|
|
40
|
+
Requires-Dist: sse-starlette>=3.0.0
|
|
41
41
|
Requires-Dist: databricks-sdk==0.120.0
|
|
42
42
|
Requires-Dist: mlflow[databricks]==3.14.0
|
|
43
43
|
Requires-Dist: mlflow-skinny==3.14.0
|
{databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app/run.py
RENAMED
|
@@ -62,18 +62,6 @@ def init_database(seed_databricks_defaults: bool = False) -> None:
|
|
|
62
62
|
logger.error(f"Failed to seed defaults: {e}\n{tb}")
|
|
63
63
|
raise SystemExit(1) from e
|
|
64
64
|
|
|
65
|
-
# Seed/load the Fernet master key pre-fork (SDR-4437 CRITICAL-3) so all
|
|
66
|
-
# uvicorn workers find the encryption_keys row already present.
|
|
67
|
-
logger.info("Ensuring encryption key...")
|
|
68
|
-
try:
|
|
69
|
-
from src.core.encryption import ensure_encryption_key
|
|
70
|
-
ensure_encryption_key()
|
|
71
|
-
logger.info("Encryption key ready")
|
|
72
|
-
except Exception as e:
|
|
73
|
-
tb = traceback.format_exc()
|
|
74
|
-
logger.error(f"Failed to ensure encryption key: {e}\n{tb}")
|
|
75
|
-
raise SystemExit(1) from e
|
|
76
|
-
|
|
77
65
|
|
|
78
66
|
def main() -> None:
|
|
79
67
|
"""Start the uvicorn server."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: databricks-tellr-app
|
|
3
|
-
Version: 0.3.13.
|
|
3
|
+
Version: 0.3.13.dev12
|
|
4
4
|
Summary: Tellr application package for Databricks Apps
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -24,7 +24,7 @@ Requires-Dist: tinycss2==1.3.0
|
|
|
24
24
|
Requires-Dist: esprima==4.0.1
|
|
25
25
|
Requires-Dist: sqlalchemy==2.0.36
|
|
26
26
|
Requires-Dist: psycopg2-binary==2.9.10
|
|
27
|
-
Requires-Dist: databricks-sql-connector
|
|
27
|
+
Requires-Dist: databricks-sql-connector>=3.0.0
|
|
28
28
|
Requires-Dist: httpx==0.28.1
|
|
29
29
|
Requires-Dist: jinja2==3.1.2
|
|
30
30
|
Requires-Dist: python-dotenv==1.2.1
|
|
@@ -32,12 +32,12 @@ Requires-Dist: pyyaml==6.0.1
|
|
|
32
32
|
Requires-Dist: pandas==2.3.3
|
|
33
33
|
Requires-Dist: python-pptx==0.6.23
|
|
34
34
|
Requires-Dist: playwright==1.48.0
|
|
35
|
-
Requires-Dist: Pillow
|
|
36
|
-
Requires-Dist: google-api-python-client
|
|
37
|
-
Requires-Dist: google-auth-oauthlib
|
|
38
|
-
Requires-Dist: google-auth-httplib2
|
|
39
|
-
Requires-Dist: cryptography
|
|
40
|
-
Requires-Dist: sse-starlette
|
|
35
|
+
Requires-Dist: Pillow>=10.0.0
|
|
36
|
+
Requires-Dist: google-api-python-client>=2.100.0
|
|
37
|
+
Requires-Dist: google-auth-oauthlib>=1.2.0
|
|
38
|
+
Requires-Dist: google-auth-httplib2>=0.2.0
|
|
39
|
+
Requires-Dist: cryptography>=42.0.0
|
|
40
|
+
Requires-Dist: sse-starlette>=3.0.0
|
|
41
41
|
Requires-Dist: databricks-sdk==0.120.0
|
|
42
42
|
Requires-Dist: mlflow[databricks]==3.14.0
|
|
43
43
|
Requires-Dist: mlflow-skinny==3.14.0
|
|
@@ -13,6 +13,7 @@ setup.py
|
|
|
13
13
|
./src/api/middleware/__init__.py
|
|
14
14
|
./src/api/middleware/request_logging.py
|
|
15
15
|
./src/api/routes/__init__.py
|
|
16
|
+
./src/api/routes/_authz.py
|
|
16
17
|
./src/api/routes/admin.py
|
|
17
18
|
./src/api/routes/admin_usage.py
|
|
18
19
|
./src/api/routes/agent_config.py
|
|
@@ -76,7 +77,6 @@ setup.py
|
|
|
76
77
|
./src/database/__init__.py
|
|
77
78
|
./src/database/models/__init__.py
|
|
78
79
|
./src/database/models/deck_contributor.py
|
|
79
|
-
./src/database/models/encryption_key.py
|
|
80
80
|
./src/database/models/feedback.py
|
|
81
81
|
./src/database/models/genie_space.py
|
|
82
82
|
./src/database/models/google_global_credentials.py
|
|
@@ -157,6 +157,7 @@ src/api/fixtures/tour_demo_deck.json
|
|
|
157
157
|
src/api/middleware/__init__.py
|
|
158
158
|
src/api/middleware/request_logging.py
|
|
159
159
|
src/api/routes/__init__.py
|
|
160
|
+
src/api/routes/_authz.py
|
|
160
161
|
src/api/routes/admin.py
|
|
161
162
|
src/api/routes/admin_usage.py
|
|
162
163
|
src/api/routes/agent_config.py
|
|
@@ -220,7 +221,6 @@ src/core/user_context.py
|
|
|
220
221
|
src/database/__init__.py
|
|
221
222
|
src/database/models/__init__.py
|
|
222
223
|
src/database/models/deck_contributor.py
|
|
223
|
-
src/database/models/encryption_key.py
|
|
224
224
|
src/database/models/feedback.py
|
|
225
225
|
src/database/models/genie_space.py
|
|
226
226
|
src/database/models/google_global_credentials.py
|
|
@@ -18,7 +18,7 @@ tinycss2==1.3.0
|
|
|
18
18
|
esprima==4.0.1
|
|
19
19
|
sqlalchemy==2.0.36
|
|
20
20
|
psycopg2-binary==2.9.10
|
|
21
|
-
databricks-sql-connector
|
|
21
|
+
databricks-sql-connector>=3.0.0
|
|
22
22
|
httpx==0.28.1
|
|
23
23
|
jinja2==3.1.2
|
|
24
24
|
python-dotenv==1.2.1
|
|
@@ -26,12 +26,12 @@ pyyaml==6.0.1
|
|
|
26
26
|
pandas==2.3.3
|
|
27
27
|
python-pptx==0.6.23
|
|
28
28
|
playwright==1.48.0
|
|
29
|
-
Pillow
|
|
30
|
-
google-api-python-client
|
|
31
|
-
google-auth-oauthlib
|
|
32
|
-
google-auth-httplib2
|
|
33
|
-
cryptography
|
|
34
|
-
sse-starlette
|
|
29
|
+
Pillow>=10.0.0
|
|
30
|
+
google-api-python-client>=2.100.0
|
|
31
|
+
google-auth-oauthlib>=1.2.0
|
|
32
|
+
google-auth-httplib2>=0.2.0
|
|
33
|
+
cryptography>=42.0.0
|
|
34
|
+
sse-starlette>=3.0.0
|
|
35
35
|
databricks-sdk==0.120.0
|
|
36
36
|
mlflow[databricks]==3.14.0
|
|
37
37
|
mlflow-skinny==3.14.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "databricks-tellr-app"
|
|
7
|
-
version = "0.3.13.
|
|
7
|
+
version = "0.3.13.dev12"
|
|
8
8
|
description = "Tellr application package for Databricks Apps"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
|
29
29
|
"esprima==4.0.1",
|
|
30
30
|
"sqlalchemy==2.0.36",
|
|
31
31
|
"psycopg2-binary==2.9.10",
|
|
32
|
-
"databricks-sql-connector
|
|
32
|
+
"databricks-sql-connector>=3.0.0",
|
|
33
33
|
"httpx==0.28.1",
|
|
34
34
|
"jinja2==3.1.2",
|
|
35
35
|
"python-dotenv==1.2.1",
|
|
@@ -37,12 +37,12 @@ dependencies = [
|
|
|
37
37
|
"pandas==2.3.3",
|
|
38
38
|
"python-pptx==0.6.23",
|
|
39
39
|
"playwright==1.48.0",
|
|
40
|
-
"Pillow
|
|
41
|
-
"google-api-python-client
|
|
42
|
-
"google-auth-oauthlib
|
|
43
|
-
"google-auth-httplib2
|
|
44
|
-
"cryptography
|
|
45
|
-
"sse-starlette
|
|
40
|
+
"Pillow>=10.0.0",
|
|
41
|
+
"google-api-python-client>=2.100.0",
|
|
42
|
+
"google-auth-oauthlib>=1.2.0",
|
|
43
|
+
"google-auth-httplib2>=0.2.0",
|
|
44
|
+
"cryptography>=42.0.0",
|
|
45
|
+
"sse-starlette>=3.0.0",
|
|
46
46
|
# --- Resolver-speedup pins: the backtracking-driver cluster ---
|
|
47
47
|
# Databricks Apps installs this wheel in a platform-managed BUILD phase with a
|
|
48
48
|
# ~10-minute budget. Left ranged, the databricks-langchain ->
|
|
@@ -58,8 +58,6 @@ dependencies = [
|
|
|
58
58
|
# via a devloop deploy if these constraints change. Formerly injected at
|
|
59
59
|
# deploy time as _RESOLVER_SPEEDUP_PINS; now carried in the wheel so a bare
|
|
60
60
|
# `pip install databricks-tellr-app` is safe.
|
|
61
|
-
# SDR-4437 MEDIUM-7 (July 2026): the 7 formerly-ranged direct deps above
|
|
62
|
-
# this block are now exact-pinned too; same refresh discipline.
|
|
63
61
|
"databricks-sdk==0.120.0",
|
|
64
62
|
"mlflow[databricks]==3.14.0",
|
|
65
63
|
"mlflow-skinny==3.14.0",
|
|
@@ -13,7 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
|
|
14
14
|
from fastapi import FastAPI, HTTPException, Request
|
|
15
15
|
from fastapi.middleware.cors import CORSMiddleware
|
|
16
|
-
from fastapi.responses import FileResponse
|
|
16
|
+
from fastapi.responses import FileResponse, JSONResponse
|
|
17
17
|
from fastapi.staticfiles import StaticFiles
|
|
18
18
|
|
|
19
19
|
from src.api.routes import admin, admin_usage, agent_config, chat, export, feedback, images, profiles, sessions, slides, tools, tour, verification, version, google_slides, setup, local_version
|
|
@@ -417,6 +417,30 @@ from src.api.middleware.request_logging import RequestLoggingMiddleware
|
|
|
417
417
|
|
|
418
418
|
app.add_middleware(RequestLoggingMiddleware)
|
|
419
419
|
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
# Exception handlers — keep BELOW the middleware-registration block above
|
|
422
|
+
# (Track A of SDR-4437 owns that block; do not reorder or interleave).
|
|
423
|
+
#
|
|
424
|
+
# HIGH-6: when user_auth_middleware fails to build the OBO client, the request
|
|
425
|
+
# used to proceed silently as the SP; now the first user-scoped call raises
|
|
426
|
+
# UserClientRequiredError, mapped here to a clean 401 instead of a 500 from
|
|
427
|
+
# deep inside a tool call.
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
from src.core.databricks_client import UserClientRequiredError
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@app.exception_handler(UserClientRequiredError)
|
|
433
|
+
async def user_client_required_handler(request: Request, exc: UserClientRequiredError):
|
|
434
|
+
logger.warning("User-scoped call without a bound OBO client: %s", exc)
|
|
435
|
+
return JSONResponse(
|
|
436
|
+
status_code=401,
|
|
437
|
+
content={
|
|
438
|
+
"detail": "Your Databricks identity could not be resolved for this "
|
|
439
|
+
"request. Please refresh the page to re-authenticate."
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
420
444
|
# Include API routers
|
|
421
445
|
app.include_router(admin.router)
|
|
422
446
|
app.include_router(admin_usage.router)
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Shared authorization helpers for API routes (SDR-4437 PR-2).
|
|
2
|
+
|
|
3
|
+
Consolidates the deck-permission helpers that previously lived in
|
|
4
|
+
``sessions.py`` and ``slides.py`` (names and signatures preserved), and adds:
|
|
5
|
+
|
|
6
|
+
- ``_require_export_job_access`` — job-ID → session_id → deck permission
|
|
7
|
+
(closes the job-ID IDOR on export poll/download and Google Slides poll).
|
|
8
|
+
- ``require_admin`` — HIGH-2 admin primitive. Admin == holds CAN_MANAGE on
|
|
9
|
+
the Databricks App itself, tested by reading the app's ACL with the
|
|
10
|
+
caller's own OBO client (reading an object's ACL requires CAN_MANAGE on
|
|
11
|
+
it, so the read succeeding *is* the admin test; group-held / inherited
|
|
12
|
+
CAN_MANAGE resolves server-side for free).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
from typing import Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from databricks.sdk.errors import PermissionDenied
|
|
21
|
+
from fastapi import HTTPException
|
|
22
|
+
from sqlalchemy.orm import Session
|
|
23
|
+
|
|
24
|
+
from src.api.services.session_manager import SessionNotFoundError, get_session_manager
|
|
25
|
+
from src.core.database import get_db_session
|
|
26
|
+
from src.core.permission_context import get_permission_context
|
|
27
|
+
from src.core.user_context import get_current_user
|
|
28
|
+
from src.database.models.profile_contributor import PermissionLevel
|
|
29
|
+
from src.services.permission_service import PERMISSION_PRIORITY, get_permission_service
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Deck-permission helpers (moved from sessions.py / slides.py — verbatim)
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_session_permission_for_info(
|
|
40
|
+
session_info: dict,
|
|
41
|
+
db: Session,
|
|
42
|
+
) -> Tuple[bool, Optional[PermissionLevel]]:
|
|
43
|
+
"""Check user's permission on a session via deck_contributors.
|
|
44
|
+
|
|
45
|
+
(Moved from sessions.py:_get_session_permission.)
|
|
46
|
+
|
|
47
|
+
Resolves to the root session (parent for contributor sessions) and checks
|
|
48
|
+
the DeckContributor table for the current user's permission level.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
session_info: Session dict with id, created_by, parent_session_id, etc.
|
|
52
|
+
db: Database session
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Tuple of (has_access, permission_level)
|
|
56
|
+
"""
|
|
57
|
+
perm_ctx = get_permission_context()
|
|
58
|
+
perm_service = get_permission_service()
|
|
59
|
+
parent_id = session_info.get("parent_session_internal_id")
|
|
60
|
+
root_session_id = parent_id if parent_id is not None else session_info.get("id")
|
|
61
|
+
perm = perm_service.get_deck_permission(
|
|
62
|
+
db, root_session_id,
|
|
63
|
+
user_id=perm_ctx.user_id if perm_ctx else None,
|
|
64
|
+
user_name=perm_ctx.user_name if perm_ctx else None,
|
|
65
|
+
group_ids=perm_ctx.group_ids if perm_ctx else None,
|
|
66
|
+
)
|
|
67
|
+
if perm is None:
|
|
68
|
+
return False, None
|
|
69
|
+
return True, perm
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_session_permission_by_id(
|
|
73
|
+
session_id: str,
|
|
74
|
+
db: Session,
|
|
75
|
+
) -> Tuple[bool, Optional[PermissionLevel]]:
|
|
76
|
+
"""Check if current user has access to a session's slides via deck_contributors.
|
|
77
|
+
|
|
78
|
+
(Moved from slides.py:_get_session_permission.)
|
|
79
|
+
|
|
80
|
+
Resolves to the root session (parent for contributor sessions) and checks
|
|
81
|
+
the DeckContributor table for the current user's permission level.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
session_id: Session identifier (string)
|
|
85
|
+
db: Database session
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Tuple of (has_access, permission_level)
|
|
89
|
+
"""
|
|
90
|
+
session_manager = get_session_manager()
|
|
91
|
+
ctx = get_permission_context()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
session_info = session_manager.get_session(session_id)
|
|
95
|
+
except SessionNotFoundError:
|
|
96
|
+
return False, None
|
|
97
|
+
|
|
98
|
+
perm_service = get_permission_service()
|
|
99
|
+
parent_internal_id = session_info.get("parent_session_internal_id")
|
|
100
|
+
root_session_id = (
|
|
101
|
+
parent_internal_id if parent_internal_id is not None else session_info.get("id")
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
perm = perm_service.get_deck_permission(
|
|
105
|
+
db, root_session_id,
|
|
106
|
+
user_id=ctx.user_id if ctx else None,
|
|
107
|
+
user_name=ctx.user_name if ctx else None,
|
|
108
|
+
group_ids=ctx.group_ids if ctx else None,
|
|
109
|
+
)
|
|
110
|
+
if perm is None:
|
|
111
|
+
return False, None
|
|
112
|
+
return True, perm
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _require_session_access(
|
|
116
|
+
session_info: dict,
|
|
117
|
+
db: Session,
|
|
118
|
+
min_permission: PermissionLevel = PermissionLevel.CAN_VIEW,
|
|
119
|
+
) -> PermissionLevel:
|
|
120
|
+
"""Require user has at least the specified permission level on a session.
|
|
121
|
+
|
|
122
|
+
(Moved from sessions.py.)
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
session_info: Session dict with created_by and profile_id
|
|
126
|
+
db: Database session
|
|
127
|
+
min_permission: Minimum required permission (default: CAN_VIEW)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The user's actual permission level
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
HTTPException 403: If user doesn't have required permission
|
|
134
|
+
"""
|
|
135
|
+
has_access, permission = _get_session_permission_for_info(session_info, db)
|
|
136
|
+
|
|
137
|
+
if not has_access or permission is None:
|
|
138
|
+
raise HTTPException(
|
|
139
|
+
status_code=403,
|
|
140
|
+
detail="You don't have permission to access this session",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if PERMISSION_PRIORITY[permission] < PERMISSION_PRIORITY[min_permission]:
|
|
144
|
+
raise HTTPException(
|
|
145
|
+
status_code=403,
|
|
146
|
+
detail=f"This action requires {min_permission.value} permission",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return permission
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _require_slide_permission(
|
|
153
|
+
session_id: str,
|
|
154
|
+
db: Session,
|
|
155
|
+
min_permission: PermissionLevel = PermissionLevel.CAN_VIEW,
|
|
156
|
+
) -> PermissionLevel:
|
|
157
|
+
"""Require user has at least the specified permission level on slides.
|
|
158
|
+
|
|
159
|
+
(Moved from slides.py.)
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
session_id: Session identifier
|
|
163
|
+
db: Database session
|
|
164
|
+
min_permission: Minimum required permission (default: CAN_VIEW)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The user's actual permission level
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
HTTPException 403: If user doesn't have required permission
|
|
171
|
+
"""
|
|
172
|
+
has_access, permission = _get_session_permission_by_id(session_id, db)
|
|
173
|
+
|
|
174
|
+
if not has_access or permission is None:
|
|
175
|
+
raise HTTPException(
|
|
176
|
+
status_code=403,
|
|
177
|
+
detail="You don't have permission to access these slides",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if PERMISSION_PRIORITY[permission] < PERMISSION_PRIORITY[min_permission]:
|
|
181
|
+
raise HTTPException(
|
|
182
|
+
status_code=403,
|
|
183
|
+
detail=f"This action requires {min_permission.value} permission",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return permission
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _check_deck_permission_for_session(
|
|
190
|
+
session_id: str,
|
|
191
|
+
min_permission: PermissionLevel = PermissionLevel.CAN_VIEW,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Look up a session by string ID, resolve root, and enforce deck permission.
|
|
194
|
+
|
|
195
|
+
(Moved from sessions.py.)
|
|
196
|
+
|
|
197
|
+
This is the standard pattern for endpoints that only have a session_id string
|
|
198
|
+
and need to gate on deck permissions. It opens its own DB session via
|
|
199
|
+
``get_db_session`` so it can be called from endpoints that do not already
|
|
200
|
+
have one.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
session_id: The string session_id passed to the endpoint.
|
|
204
|
+
min_permission: Minimum required permission level.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
HTTPException 404: If the session does not exist (stale tab, deleted session, wrong ID).
|
|
208
|
+
HTTPException 403: If the caller lacks the required permission.
|
|
209
|
+
"""
|
|
210
|
+
session_manager = get_session_manager()
|
|
211
|
+
try:
|
|
212
|
+
session_info = session_manager.get_session(session_id)
|
|
213
|
+
except SessionNotFoundError:
|
|
214
|
+
raise HTTPException(
|
|
215
|
+
status_code=404,
|
|
216
|
+
detail=f"Session not found: {session_id}",
|
|
217
|
+
) from None
|
|
218
|
+
with get_db_session() as db:
|
|
219
|
+
_require_session_access(session_info, db, min_permission)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
# Job-ID access (export poll/download, Google Slides poll)
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _require_export_job_access(
|
|
228
|
+
job_id: str,
|
|
229
|
+
min_permission: PermissionLevel = PermissionLevel.CAN_VIEW,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Resolve an export job's session and enforce deck permission on it.
|
|
232
|
+
|
|
233
|
+
Every ExportJob row is session-bound (both enqueue sites require a
|
|
234
|
+
session_id), so possession of a job_id must not grant access to another
|
|
235
|
+
user's export — the caller needs ``min_permission`` on the job's deck.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
HTTPException 404: unknown job_id.
|
|
239
|
+
HTTPException 404/403: from the deck-permission check.
|
|
240
|
+
"""
|
|
241
|
+
from src.database.models.session import ExportJob
|
|
242
|
+
|
|
243
|
+
with get_db_session() as db:
|
|
244
|
+
job = db.query(ExportJob).filter(ExportJob.job_id == job_id).first()
|
|
245
|
+
session_id = job.session_id if job is not None else None
|
|
246
|
+
if session_id is None:
|
|
247
|
+
raise HTTPException(status_code=404, detail="Export job not found")
|
|
248
|
+
_check_deck_permission_for_session(session_id, min_permission)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ---------------------------------------------------------------------------
|
|
252
|
+
# HIGH-2: admin primitive — App CAN_MANAGE via OBO self-test
|
|
253
|
+
# ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
_ADMIN_CACHE: dict = {}
|
|
256
|
+
_ADMIN_CACHE_TTL_SECONDS = 60.0
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _is_production() -> bool:
|
|
260
|
+
return os.getenv("ENVIRONMENT", "development") == "production"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _admin_acl_probe(user_name: str) -> bool:
|
|
264
|
+
"""Return True iff the caller's OBO client can read the app's ACL.
|
|
265
|
+
|
|
266
|
+
Reading an object's ACL in Databricks requires CAN_MANAGE on it, so a
|
|
267
|
+
successful ``apps.get_permissions`` call with the caller's own token is
|
|
268
|
+
the admin test. A CAN_USE-only caller raises ``PermissionDenied``, which
|
|
269
|
+
``require_admin`` treats as a definitive (cacheable) deny; any other
|
|
270
|
+
error (missing OBO client, API/network failure) fails closed for the
|
|
271
|
+
current request without being cached. Missing app name -> False.
|
|
272
|
+
``user_name`` is for logging/attribution only — the *token* is what is
|
|
273
|
+
being tested.
|
|
274
|
+
"""
|
|
275
|
+
app_name = os.environ.get("DATABRICKS_APP_NAME")
|
|
276
|
+
if not app_name:
|
|
277
|
+
logger.warning("require_admin: DATABRICKS_APP_NAME unset; failing closed")
|
|
278
|
+
return False
|
|
279
|
+
from src.core.databricks_client import get_user_client
|
|
280
|
+
|
|
281
|
+
client = get_user_client()
|
|
282
|
+
client.apps.get_permissions(app_name)
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def require_admin() -> None:
|
|
287
|
+
"""FastAPI dependency: caller must hold CAN_MANAGE on the Databricks App.
|
|
288
|
+
|
|
289
|
+
- Local dev/test: bypass (dev auth is DEV_USER_ID with no token or ACL
|
|
290
|
+
behind it) — same pattern as the dev-only CORS gate in main.py.
|
|
291
|
+
- Verdicts cached per-username with a short TTL. The cache is in-memory
|
|
292
|
+
and therefore per-worker — deliberately fine here: a cache miss on
|
|
293
|
+
another worker just re-runs the ACL lookup; correctness never depends
|
|
294
|
+
on which worker got the request.
|
|
295
|
+
- Only *definitive* verdicts are cached: a probe result, or a
|
|
296
|
+
``PermissionDenied`` from the API (the token demonstrably lacks
|
|
297
|
+
CAN_MANAGE). Transient probe errors fail closed for the current
|
|
298
|
+
request but are NOT cached — otherwise one network blip would lock a
|
|
299
|
+
real admin out for the full TTL.
|
|
300
|
+
- Fail closed (403) on non-admin, missing user, missing OBO client, or
|
|
301
|
+
lookup error.
|
|
302
|
+
"""
|
|
303
|
+
if not _is_production():
|
|
304
|
+
return
|
|
305
|
+
user = get_current_user()
|
|
306
|
+
if not user:
|
|
307
|
+
raise HTTPException(status_code=403, detail="Admin access required")
|
|
308
|
+
|
|
309
|
+
now = time.monotonic()
|
|
310
|
+
cached = _ADMIN_CACHE.get(user)
|
|
311
|
+
if cached is not None and now - cached[1] < _ADMIN_CACHE_TTL_SECONDS:
|
|
312
|
+
verdict = cached[0]
|
|
313
|
+
else:
|
|
314
|
+
try:
|
|
315
|
+
verdict = _admin_acl_probe(user)
|
|
316
|
+
_ADMIN_CACHE[user] = (verdict, now)
|
|
317
|
+
except PermissionDenied:
|
|
318
|
+
# Definitive API answer: the token lacks CAN_MANAGE. Cacheable.
|
|
319
|
+
verdict = False
|
|
320
|
+
_ADMIN_CACHE[user] = (verdict, now)
|
|
321
|
+
except Exception:
|
|
322
|
+
logger.warning(
|
|
323
|
+
"require_admin: ACL probe failed for %s; failing closed (uncached)",
|
|
324
|
+
user,
|
|
325
|
+
exc_info=True,
|
|
326
|
+
)
|
|
327
|
+
# Fail closed on THIS request only — do not cache error-derived
|
|
328
|
+
# denials (a transient blip must not deny an admin for the TTL).
|
|
329
|
+
verdict = False
|
|
330
|
+
|
|
331
|
+
if not verdict:
|
|
332
|
+
raise HTTPException(status_code=403, detail="Admin access required")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def reset_admin_cache() -> None:
|
|
336
|
+
"""Clear the admin-verdict cache (tests)."""
|
|
337
|
+
_ADMIN_CACHE.clear()
|
{databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin.py
RENAMED
|
@@ -9,6 +9,7 @@ from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
from sqlalchemy.orm import Session
|
|
11
11
|
|
|
12
|
+
from src.api.routes._authz import require_admin
|
|
12
13
|
from src.api.utils.validation import validate_credentials_json
|
|
13
14
|
from src.core.database import get_db
|
|
14
15
|
from src.core.encryption import decrypt_data, encrypt_data
|
|
@@ -23,7 +24,10 @@ from src.database.models.google_oauth_token import GoogleOAuthToken
|
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
# SDR-4437 HIGH-2: entire admin surface requires App CAN_MANAGE.
|
|
28
|
+
router = APIRouter(
|
|
29
|
+
prefix="/api/admin", tags=["admin"], dependencies=[Depends(require_admin)]
|
|
30
|
+
)
|
|
27
31
|
|
|
28
32
|
_MAX_CREDENTIALS_SIZE = 100 * 1024
|
|
29
33
|
|
{databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin_usage.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Admin usage-analytics endpoints (
|
|
1
|
+
"""Admin usage-analytics endpoints (admin-gated, like the rest of /admin)."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import date as dt_date
|
|
@@ -7,11 +7,18 @@ from datetime import datetime
|
|
|
7
7
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
8
8
|
from sqlalchemy.orm import Session
|
|
9
9
|
|
|
10
|
+
from src.api.routes._authz import require_admin
|
|
10
11
|
from src.api.services.usage_service import MAX_WINDOW_DAYS, UsageService
|
|
11
12
|
from src.core.database import get_db
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
# SDR-4437 MEDIUM-6: workspace-wide usage analytics are admin-only.
|
|
17
|
+
router = APIRouter(
|
|
18
|
+
prefix="/api/admin/usage",
|
|
19
|
+
tags=["admin-usage"],
|
|
20
|
+
dependencies=[Depends(require_admin)],
|
|
21
|
+
)
|
|
15
22
|
|
|
16
23
|
|
|
17
24
|
def _parse_date(value: str, name: str) -> dt_date:
|
|
@@ -7,10 +7,12 @@ from typing import Literal
|
|
|
7
7
|
from fastapi import APIRouter, HTTPException
|
|
8
8
|
from pydantic import BaseModel, Field, ValidationError
|
|
9
9
|
|
|
10
|
+
from src.api.routes._authz import _check_deck_permission_for_session
|
|
10
11
|
from src.api.schemas.agent_config import AgentConfig, ToolEntry, resolve_agent_config
|
|
11
12
|
from src.api.services.session_manager import SessionNotFoundError, get_session_manager
|
|
12
13
|
from src.core.database import get_db_session
|
|
13
14
|
from src.database.models import UserSession
|
|
15
|
+
from src.database.models.profile_contributor import PermissionLevel
|
|
14
16
|
|
|
15
17
|
logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
@@ -83,6 +85,8 @@ class PatchToolRequest(BaseModel):
|
|
|
83
85
|
@router.get("")
|
|
84
86
|
async def get_agent_config(session_id: str):
|
|
85
87
|
"""Return the current agent config for a session (defaults if null)."""
|
|
88
|
+
# SDR-4437 HIGH-1: reading a session's agent config requires CAN_VIEW.
|
|
89
|
+
_check_deck_permission_for_session(session_id, PermissionLevel.CAN_VIEW)
|
|
86
90
|
try:
|
|
87
91
|
mgr = get_session_manager()
|
|
88
92
|
session = await asyncio.to_thread(mgr.get_session, session_id)
|
|
@@ -99,6 +103,8 @@ async def get_agent_config(session_id: str):
|
|
|
99
103
|
@router.put("")
|
|
100
104
|
async def put_agent_config(session_id: str, config: AgentConfig):
|
|
101
105
|
"""Replace the full agent config for a session."""
|
|
106
|
+
# SDR-4437 HIGH-1: agent-config writes repoint the session's tools — CAN_MANAGE.
|
|
107
|
+
_check_deck_permission_for_session(session_id, PermissionLevel.CAN_MANAGE)
|
|
102
108
|
# Pydantic already validated duplicates via model_validator.
|
|
103
109
|
# Now validate foreign-key references.
|
|
104
110
|
_validate_references(config)
|
|
@@ -114,6 +120,8 @@ async def put_agent_config(session_id: str, config: AgentConfig):
|
|
|
114
120
|
@router.patch("/tools")
|
|
115
121
|
async def patch_tools(session_id: str, body: PatchToolRequest):
|
|
116
122
|
"""Add or remove a single tool from the session's agent config."""
|
|
123
|
+
# SDR-4437 HIGH-1: agent-config writes repoint the session's tools — CAN_MANAGE.
|
|
124
|
+
_check_deck_permission_for_session(session_id, PermissionLevel.CAN_MANAGE)
|
|
117
125
|
try:
|
|
118
126
|
mgr = get_session_manager()
|
|
119
127
|
session = await asyncio.to_thread(mgr.get_session, session_id)
|