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.
Files changed (151) hide show
  1. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/PKG-INFO +8 -8
  2. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app/run.py +0 -12
  3. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/PKG-INFO +8 -8
  4. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/SOURCES.txt +2 -2
  5. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/requires.txt +7 -7
  6. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/pyproject.toml +8 -10
  7. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/main.py +25 -1
  8. databricks_tellr_app-0.3.13.dev12/src/api/routes/_authz.py +337 -0
  9. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin.py +5 -1
  10. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/admin_usage.py +9 -2
  11. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/agent_config.py +8 -0
  12. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/chat.py +13 -0
  13. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/export.py +87 -67
  14. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/feedback.py +4 -3
  15. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/google_slides.py +28 -6
  16. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/images.py +36 -7
  17. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/profiles.py +6 -0
  18. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/sessions.py +5 -100
  19. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/deck_prompts.py +33 -21
  20. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/slide_styles.py +35 -22
  21. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/slides.py +67 -109
  22. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/tour.py +4 -0
  23. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/verification.py +13 -0
  24. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/databricks_client.py +33 -6
  25. databricks_tellr_app-0.3.13.dev12/src/core/encryption.py +86 -0
  26. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/lakebase.py +114 -0
  27. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/__init__.py +0 -2
  28. databricks_tellr_app-0.3.13.dev4/src/core/encryption.py +0 -125
  29. databricks_tellr_app-0.3.13.dev4/src/database/models/encryption_key.py +0 -30
  30. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/README.md +0 -0
  31. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app/__init__.py +0 -0
  32. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  33. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  34. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/setup.cfg +0 -0
  35. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/setup.py +0 -0
  36. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/__init__.py +0 -0
  37. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/__init__.py +0 -0
  38. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/_otel_bootstrap.py +0 -0
  39. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/fixtures/tour_demo_deck.json +0 -0
  40. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/mcp_auth.py +0 -0
  41. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/mcp_server.py +0 -0
  42. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/middleware/__init__.py +0 -0
  43. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/middleware/request_logging.py +0 -0
  44. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/__init__.py +0 -0
  45. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/deck_contributors.py +0 -0
  46. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/local_version.py +0 -0
  47. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/__init__.py +0 -0
  48. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/contributors.py +0 -0
  49. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/settings/identities.py +0 -0
  50. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/setup.py +0 -0
  51. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/tools.py +0 -0
  52. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/routes/version.py +0 -0
  53. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/__init__.py +0 -0
  54. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/agent_config.py +0 -0
  55. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/feedback.py +0 -0
  56. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/requests.py +0 -0
  57. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/responses.py +0 -0
  58. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/__init__.py +0 -0
  59. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/requests.py +0 -0
  60. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/settings/responses.py +0 -0
  61. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/schemas/streaming.py +0 -0
  62. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/__init__.py +0 -0
  63. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/chat_service.py +0 -0
  64. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/export_job_queue.py +0 -0
  65. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/feedback_service.py +0 -0
  66. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/job_queue.py +0 -0
  67. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/session_manager.py +0 -0
  68. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/session_naming.py +0 -0
  69. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/usage_events.py +0 -0
  70. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/services/usage_service.py +0 -0
  71. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/utils/__init__.py +0 -0
  72. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/api/utils/validation.py +0 -0
  73. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/__init__.py +0 -0
  74. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/config_loader.py +0 -0
  75. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/context_utils.py +0 -0
  76. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/database.py +0 -0
  77. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/defaults.py +0 -0
  78. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/init_default_profile.py +0 -0
  79. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/migrate_profiles_to_agent_config.py +0 -0
  80. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/mlflow_agent_spans.py +0 -0
  81. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/mlflow_tracing.py +0 -0
  82. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/otel_logging.py +0 -0
  83. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/permission_context.py +0 -0
  84. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/prompt_modules.py +0 -0
  85. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/settings_db.py +0 -0
  86. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/core/user_context.py +0 -0
  87. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/__init__.py +0 -0
  88. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/deck_contributor.py +0 -0
  89. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/feedback.py +0 -0
  90. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/genie_space.py +0 -0
  91. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/google_global_credentials.py +0 -0
  92. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/google_oauth_token.py +0 -0
  93. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/identity.py +0 -0
  94. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/image.py +0 -0
  95. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/profile.py +0 -0
  96. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/profile_contributor.py +0 -0
  97. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/prompts.py +0 -0
  98. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/request_log.py +0 -0
  99. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/session.py +0 -0
  100. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/slide_deck_prompt.py +0 -0
  101. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/slide_style_library.py +0 -0
  102. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/usage_event.py +0 -0
  103. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/database/models/user_preference.py +0 -0
  104. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/__init__.py +0 -0
  105. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/slide.py +0 -0
  106. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/domain/slide_deck.py +0 -0
  107. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/__init__.py +0 -0
  108. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/agent.py +0 -0
  109. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/agent_factory.py +0 -0
  110. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/config_service.py +0 -0
  111. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/config_validator.py +0 -0
  112. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/drive_uploader.py +0 -0
  113. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/evaluation/__init__.py +0 -0
  114. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/evaluation/llm_judge.py +0 -0
  115. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/genie_service.py +0 -0
  116. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/google_slides_auth.py +0 -0
  117. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/google_slides_prompts_defaults.py +0 -0
  118. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/html_to_google_slides.py +0 -0
  119. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/html_to_pptx.py +0 -0
  120. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_provider.py +0 -0
  121. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/__init__.py +0 -0
  122. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/account_provider.py +0 -0
  123. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/local_provider.py +0 -0
  124. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/identity_providers/workspace_provider.py +0 -0
  125. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/image_service.py +0 -0
  126. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/image_tools.py +0 -0
  127. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/permission_service.py +0 -0
  128. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_from_html_huashu.py +0 -0
  129. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_from_records.py +0 -0
  130. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/pptx_prompts_defaults.py +0 -0
  131. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/profile_service.py +0 -0
  132. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/streaming_callback.py +0 -0
  133. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/__init__.py +0 -0
  134. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/agent_bricks_tool.py +0 -0
  135. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/genie_tool.py +0 -0
  136. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/mcp_tool.py +0 -0
  137. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/model_endpoint_tool.py +0 -0
  138. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/tools/vector_tool.py +0 -0
  139. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/services/validator.py +0 -0
  140. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/__init__.py +0 -0
  141. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/css_utils.py +0 -0
  142. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/error_handling.py +0 -0
  143. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/html_safety.py +0 -0
  144. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/html_utils.py +0 -0
  145. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/image_utils.py +0 -0
  146. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/js_validator.py +0 -0
  147. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/logging_config.py +0 -0
  148. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/pi_filter.py +0 -0
  149. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/slide_hash.py +0 -0
  150. {databricks_tellr_app-0.3.13.dev4 → databricks_tellr_app-0.3.13.dev12}/src/utils/spotlight.py +0 -0
  151. {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.dev4
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==3.0.1
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==11.3.0
36
- Requires-Dist: google-api-python-client==2.190.0
37
- Requires-Dist: google-auth-oauthlib==1.2.4
38
- Requires-Dist: google-auth-httplib2==0.3.0
39
- Requires-Dist: cryptography==46.0.3
40
- Requires-Dist: sse-starlette==3.0.3
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
@@ -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.dev4
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==3.0.1
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==11.3.0
36
- Requires-Dist: google-api-python-client==2.190.0
37
- Requires-Dist: google-auth-oauthlib==1.2.4
38
- Requires-Dist: google-auth-httplib2==0.3.0
39
- Requires-Dist: cryptography==46.0.3
40
- Requires-Dist: sse-starlette==3.0.3
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==3.0.1
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==11.3.0
30
- google-api-python-client==2.190.0
31
- google-auth-oauthlib==1.2.4
32
- google-auth-httplib2==0.3.0
33
- cryptography==46.0.3
34
- sse-starlette==3.0.3
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.dev4"
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==3.0.1",
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==11.3.0",
41
- "google-api-python-client==2.190.0",
42
- "google-auth-oauthlib==1.2.4",
43
- "google-auth-httplib2==0.3.0",
44
- "cryptography==46.0.3",
45
- "sse-starlette==3.0.3",
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()
@@ -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
- router = APIRouter(prefix="/api/admin", tags=["admin"])
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
 
@@ -1,4 +1,4 @@
1
- """Admin usage-analytics endpoints (ungated, like the rest of /admin)."""
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
- router = APIRouter(prefix="/api/admin/usage", tags=["admin-usage"])
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)