gaard-api 0.2.3__tar.gz → 0.2.5__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 (74) hide show
  1. {gaard_api-0.2.3/src/gaard_api.egg-info → gaard_api-0.2.5}/PKG-INFO +6 -5
  2. {gaard_api-0.2.3 → gaard_api-0.2.5}/pyproject.toml +6 -5
  3. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/database.py +31 -4
  4. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/models.py +113 -0
  5. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/services.py +24 -0
  6. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin-web/assets/main.js +329 -368
  7. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin-web/assets/styles.css +125 -40
  8. gaard_api-0.2.5/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack-all.js +3 -0
  9. gaard_api-0.2.5/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack.min.css +1 -0
  10. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin-web/index.html +2 -0
  11. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/v1/admin.py +390 -52
  12. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/v1/analysis.py +242 -32
  13. gaard_api-0.2.5/src/gaard_api/api/v1/dashboards.py +567 -0
  14. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/v1/prompts.py +3 -1
  15. gaard_api-0.2.5/src/gaard_api/api/v1/query.py +1618 -0
  16. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/v1/schema.py +8 -3
  17. gaard_api-0.2.5/src/gaard_api/auth_dependencies.py +98 -0
  18. gaard_api-0.2.5/src/gaard_api/auth_hooks.py +53 -0
  19. gaard_api-0.2.5/src/gaard_api/conversations.py +476 -0
  20. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/core/settings.py +7 -1
  21. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/example_data/medical_poc/seed.sql +206 -50
  22. gaard_api-0.2.5/src/gaard_api/example_database.py +474 -0
  23. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/extensions.py +66 -0
  24. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/license.py +12 -3
  25. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/main.py +3 -1
  26. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/package_updates.py +1 -1
  27. gaard_api-0.2.5/src/gaard_api/siem.py +184 -0
  28. {gaard_api-0.2.3 → gaard_api-0.2.5/src/gaard_api.egg-info}/PKG-INFO +6 -5
  29. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api.egg-info/SOURCES.txt +8 -0
  30. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api.egg-info/requires.txt +5 -4
  31. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_admin_api.py +416 -69
  32. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_analysis_api.py +181 -18
  33. gaard_api-0.2.5/tests/test_conversation_api.py +489 -0
  34. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_error_handlers.py +38 -0
  35. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_license_api.py +0 -13
  36. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_license_service.py +27 -0
  37. gaard_api-0.2.5/tests/test_server_cli.py +215 -0
  38. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_settings.py +0 -1
  39. gaard_api-0.2.3/src/gaard_api/api/v1/query.py +0 -758
  40. gaard_api-0.2.3/src/gaard_api/example_database.py +0 -135
  41. gaard_api-0.2.3/tests/test_server_cli.py +0 -111
  42. {gaard_api-0.2.3 → gaard_api-0.2.5}/MANIFEST.in +0 -0
  43. {gaard_api-0.2.3 → gaard_api-0.2.5}/README.md +0 -0
  44. {gaard_api-0.2.3 → gaard_api-0.2.5}/setup.cfg +0 -0
  45. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/__init__.py +0 -0
  46. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/__init__.py +0 -0
  47. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/defaults.py +0 -0
  48. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/prompt_runtime.py +0 -0
  49. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin/security.py +0 -0
  50. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin-web/assets/getgaard.svg +0 -0
  51. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/admin-web/package.json +0 -0
  52. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/__init__.py +0 -0
  53. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api/v1/__init__.py +0 -0
  54. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/api_registry.py +0 -0
  55. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/cli.py +0 -0
  56. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/cli_commands.py +0 -0
  57. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/core/__init__.py +0 -0
  58. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/core/error_handlers.py +0 -0
  59. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/core/schema_cache.py +0 -0
  60. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/example_data/__init__.py +0 -0
  61. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
  62. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
  63. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/extension_services.py +0 -0
  64. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/py.typed +0 -0
  65. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/query_hooks.py +0 -0
  66. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/server_cli.py +0 -0
  67. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api/tls_http.py +0 -0
  68. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api.egg-info/dependency_links.txt +0 -0
  69. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api.egg-info/entry_points.txt +0 -0
  70. {gaard_api-0.2.3 → gaard_api-0.2.5}/src/gaard_api.egg-info/top_level.txt +0 -0
  71. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_api_registry.py +0 -0
  72. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_prompt_runtime.py +0 -0
  73. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_query_error_messages.py +0 -0
  74. {gaard_api-0.2.3 → gaard_api-0.2.5}/tests/test_tls_http.py +0 -0
@@ -1,16 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaard-api
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: GAARD backend web services providing admin interface
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: gaard-core==0.2.2
8
- Requires-Dist: gaard-connectors==0.2.2
9
- Requires-Dist: gaard-llm==0.2.2
10
- Requires-Dist: gaard-plugin-api<0.3.0,>=0.2.2
7
+ Requires-Dist: gaard-core==0.2.4
8
+ Requires-Dist: gaard-connectors==0.2.4
9
+ Requires-Dist: gaard-llm==0.2.4
10
+ Requires-Dist: gaard-plugin-api<0.3.0,>=0.2.4
11
11
  Requires-Dist: fastapi>=0.111.0
12
12
  Requires-Dist: uvicorn[standard]>=0.30.0
13
13
  Requires-Dist: pydantic>=2.7.0
14
+ Requires-Dist: python-multipart>=0.0.9
14
15
  Requires-Dist: sqlalchemy>=2.0.0
15
16
  Requires-Dist: httpx2>=2.5.0
16
17
  Requires-Dist: certifi>=2024.8.30
@@ -4,19 +4,20 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gaard-api"
7
- version = "0.2.3"
7
+ version = "0.2.5"
8
8
  description = "GAARD backend web services providing admin interface"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
 
12
12
  dependencies = [
13
- "gaard-core==0.2.2",
14
- "gaard-connectors==0.2.2",
15
- "gaard-llm==0.2.2",
16
- "gaard-plugin-api>=0.2.2,<0.3.0",
13
+ "gaard-core==0.2.4",
14
+ "gaard-connectors==0.2.4",
15
+ "gaard-llm==0.2.4",
16
+ "gaard-plugin-api>=0.2.4,<0.3.0",
17
17
  "fastapi>=0.111.0",
18
18
  "uvicorn[standard]>=0.30.0",
19
19
  "pydantic>=2.7.0",
20
+ "python-multipart>=0.0.9",
20
21
  "sqlalchemy>=2.0.0",
21
22
  "httpx2>=2.5.0",
22
23
  "certifi>=2024.8.30"
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session, sessionmaker
10
10
  from gaard_api.admin.defaults import DEFAULT_GOVERNANCE_POLICY_CONFIG, DEFAULT_PROMPTS
11
11
  from gaard_api.admin.models import (
12
12
  AdminSetting,
13
+ AdminSession,
13
14
  AdminUser,
14
15
  Base,
15
16
  DataQueryAuditLog,
@@ -73,6 +74,7 @@ def init_metadata_store() -> None:
73
74
  with _init_lock:
74
75
  engine = get_engine()
75
76
  Base.metadata.create_all(engine)
77
+ ensure_admin_session_schema(engine)
76
78
  ensure_data_query_audit_schema(engine)
77
79
  ensure_overview_widget_schema(engine)
78
80
 
@@ -339,8 +341,8 @@ def seed_overview_widgets(session: Session) -> None:
339
341
  ),
340
342
  "sql": runtime_sql,
341
343
  "result_mode": "data",
342
- "position": 100,
343
- "grid_width": 4,
344
+ "position": 250,
345
+ "grid_width": 12,
344
346
  "active": False,
345
347
  },
346
348
  {
@@ -358,8 +360,8 @@ def seed_overview_widgets(session: Session) -> None:
358
360
  "ORDER BY prompt_key"
359
361
  ),
360
362
  "result_mode": "data",
361
- "position": 50,
362
- "grid_width": 4,
363
+ "position": 130,
364
+ "grid_width": 12,
363
365
  },
364
366
  ]
365
367
 
@@ -430,6 +432,31 @@ def ensure_data_query_audit_schema(engine: Engine) -> None:
430
432
  index.create(bind=connection, checkfirst=True)
431
433
 
432
434
 
435
+ def ensure_admin_session_schema(engine: Engine) -> None:
436
+ inspector = inspect(engine)
437
+
438
+ if "admin_sessions" not in inspector.get_table_names():
439
+ return
440
+
441
+ columns = {column["name"] for column in inspector.get_columns("admin_sessions")}
442
+
443
+ additions = {
444
+ "username": "ALTER TABLE admin_sessions ADD COLUMN username VARCHAR(255) DEFAULT ''",
445
+ "role": "ALTER TABLE admin_sessions ADD COLUMN role VARCHAR(50) DEFAULT 'admin'",
446
+ "auth_provider": (
447
+ "ALTER TABLE admin_sessions ADD COLUMN auth_provider VARCHAR(255) DEFAULT 'local'"
448
+ ),
449
+ }
450
+ with engine.begin() as connection:
451
+ for column_name, sql in additions.items():
452
+ if column_name not in columns:
453
+ connection.execute(text(sql))
454
+
455
+ session_table = cast(Table, AdminSession.__table__)
456
+ for index in session_table.indexes:
457
+ index.create(bind=connection, checkfirst=True)
458
+
459
+
433
460
  def backfill_data_query_audit_types(session: Session) -> None:
434
461
  logs = session.scalars(
435
462
  select(DataQueryAuditLog).where(
@@ -45,6 +45,9 @@ class AdminSession(Base):
45
45
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
46
46
  token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
47
47
  user_id: Mapped[int] = mapped_column(Integer, index=True)
48
+ username: Mapped[str] = mapped_column(String(255), index=True, default="")
49
+ role: Mapped[str] = mapped_column(String(50), index=True, default="admin")
50
+ auth_provider: Mapped[str] = mapped_column(String(255), index=True, default="local")
48
51
  created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
49
52
 
50
53
 
@@ -106,6 +109,116 @@ class DataQueryAuditLog(Base):
106
109
  metadata_json: Mapped[str] = mapped_column(Text, default="{}")
107
110
 
108
111
 
112
+ class Conversation(Base):
113
+ __tablename__ = "conversations"
114
+
115
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
116
+ conversation_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
117
+ owner_user_id: Mapped[str] = mapped_column(String(255), index=True)
118
+ owner_username: Mapped[str] = mapped_column(String(255), index=True, default="")
119
+ status: Mapped[str] = mapped_column(String(50), index=True, default="active")
120
+ datasource_id: Mapped[str] = mapped_column(String(255), index=True, default="")
121
+ datasource_ids_json: Mapped[str] = mapped_column(Text, default="[]")
122
+ title: Mapped[str] = mapped_column(String(255), default="")
123
+ summary_json: Mapped[str] = mapped_column(Text, default="{}")
124
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
125
+ updated_at: Mapped[datetime] = mapped_column(
126
+ DateTime(timezone=True),
127
+ default=utc_now,
128
+ onupdate=utc_now,
129
+ )
130
+
131
+
132
+ class ConversationTurn(Base):
133
+ __tablename__ = "conversation_turns"
134
+
135
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
136
+ turn_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
137
+ conversation_id: Mapped[str] = mapped_column(String(64), index=True)
138
+ mode: Mapped[str] = mapped_column(String(50), index=True, default="sql")
139
+ status: Mapped[str] = mapped_column(String(50), index=True, default="completed")
140
+ original_question: Mapped[str] = mapped_column(Text)
141
+ standalone_question: Mapped[str] = mapped_column(Text, default="")
142
+ answer: Mapped[str] = mapped_column(Text, default="")
143
+ sql: Mapped[str] = mapped_column(Text, default="")
144
+ metadata_json: Mapped[str] = mapped_column(Text, default="{}")
145
+ data_query_audit_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
146
+ analysis_session_id: Mapped[str] = mapped_column(String(64), index=True, default="")
147
+ context_decision: Mapped[str] = mapped_column(String(50), index=True, default="new_topic")
148
+ context_confidence: Mapped[float] = mapped_column(Float, default=0.0)
149
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
150
+
151
+
152
+ class Dashboard(Base):
153
+ __tablename__ = "dashboards"
154
+
155
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
156
+ dashboard_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
157
+ owner_user_id: Mapped[str] = mapped_column(String(255), index=True)
158
+ owner_username: Mapped[str] = mapped_column(String(255), index=True, default="")
159
+ name: Mapped[str] = mapped_column(String(255))
160
+ description: Mapped[str] = mapped_column(Text, default="")
161
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
162
+ updated_at: Mapped[datetime] = mapped_column(
163
+ DateTime(timezone=True),
164
+ default=utc_now,
165
+ onupdate=utc_now,
166
+ )
167
+
168
+
169
+ class DashboardUserState(Base):
170
+ __tablename__ = "dashboard_user_states"
171
+
172
+ owner_user_id: Mapped[str] = mapped_column(String(255), primary_key=True)
173
+ owner_username: Mapped[str] = mapped_column(String(255), index=True, default="")
174
+ active_dashboard_id: Mapped[str] = mapped_column(String(64), index=True, default="")
175
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
176
+ updated_at: Mapped[datetime] = mapped_column(
177
+ DateTime(timezone=True),
178
+ default=utc_now,
179
+ onupdate=utc_now,
180
+ )
181
+
182
+
183
+ class UserSavedMetric(Base):
184
+ __tablename__ = "user_saved_metrics"
185
+ __table_args__ = (
186
+ UniqueConstraint("owner_user_id", "widget_key", name="uq_user_saved_metrics_owner_widget"),
187
+ )
188
+
189
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
190
+ owner_user_id: Mapped[str] = mapped_column(String(255), index=True)
191
+ owner_username: Mapped[str] = mapped_column(String(255), index=True, default="")
192
+ widget_key: Mapped[str] = mapped_column(String(255), index=True)
193
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
194
+
195
+
196
+ class DashboardWidget(Base):
197
+ __tablename__ = "dashboard_widgets"
198
+ __table_args__ = (
199
+ UniqueConstraint("widget_id", name="uq_dashboard_widgets_widget_id"),
200
+ )
201
+
202
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
203
+ widget_id: Mapped[str] = mapped_column(String(64), index=True)
204
+ dashboard_id: Mapped[str] = mapped_column(String(64), index=True)
205
+ owner_user_id: Mapped[str] = mapped_column(String(255), index=True)
206
+ owner_username: Mapped[str] = mapped_column(String(255), index=True, default="")
207
+ metric_widget_key: Mapped[str] = mapped_column(String(255), index=True)
208
+ title: Mapped[str] = mapped_column(String(255))
209
+ visualization_type: Mapped[str] = mapped_column(String(50), default="table")
210
+ x: Mapped[int] = mapped_column(Integer, default=0)
211
+ y: Mapped[int] = mapped_column(Integer, default=0)
212
+ w: Mapped[int] = mapped_column(Integer, default=6)
213
+ h: Mapped[int] = mapped_column(Integer, default=4)
214
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
215
+ updated_at: Mapped[datetime] = mapped_column(
216
+ DateTime(timezone=True),
217
+ default=utc_now,
218
+ onupdate=utc_now,
219
+ )
220
+
221
+
109
222
  class PromptTemplate(Base):
110
223
  __tablename__ = "prompt_templates"
111
224
  __table_args__ = (UniqueConstraint("prompt_key", name="uq_prompt_templates_key"),)
@@ -33,6 +33,12 @@ from gaard_api.admin.models import (
33
33
  )
34
34
  from gaard_api.core.settings import settings
35
35
  from gaard_api.extensions import get_connector_registry
36
+ from gaard_api.siem import (
37
+ build_admin_audit_event,
38
+ build_data_query_audit_event,
39
+ dispatch_siem_event,
40
+ queue_siem_event,
41
+ )
36
42
 
37
43
 
38
44
  def json_dumps(value: Any) -> str:
@@ -289,6 +295,16 @@ def record_admin_audit(
289
295
  details_json=json_dumps(details or {}),
290
296
  )
291
297
  )
298
+ queue_siem_event(
299
+ session,
300
+ build_admin_audit_event(
301
+ actor=actor,
302
+ action=action,
303
+ resource_type=resource_type,
304
+ resource_id=resource_id,
305
+ details=details,
306
+ ),
307
+ )
292
308
 
293
309
 
294
310
  DATA_QUERY_AUDIT_INFO = DataQueryAuditType.INFO.value
@@ -499,6 +515,14 @@ def _record_data_query_audit(
499
515
  )
500
516
  session.add(log)
501
517
  session.commit()
518
+ dispatch_siem_event(
519
+ build_data_query_audit_event(
520
+ request.user_id,
521
+ request.datasource_id,
522
+ log,
523
+ audit_metadata,
524
+ )
525
+ )
502
526
  return log
503
527
  except SQLAlchemyError:
504
528
  session.rollback()