kailash 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

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 (146) hide show
  1. kailash/__init__.py +33 -1
  2. kailash/access_control/__init__.py +129 -0
  3. kailash/access_control/managers.py +461 -0
  4. kailash/access_control/rule_evaluators.py +467 -0
  5. kailash/access_control_abac.py +825 -0
  6. kailash/config/__init__.py +27 -0
  7. kailash/config/database_config.py +359 -0
  8. kailash/database/__init__.py +28 -0
  9. kailash/database/execution_pipeline.py +499 -0
  10. kailash/middleware/__init__.py +306 -0
  11. kailash/middleware/auth/__init__.py +33 -0
  12. kailash/middleware/auth/access_control.py +436 -0
  13. kailash/middleware/auth/auth_manager.py +422 -0
  14. kailash/middleware/auth/jwt_auth.py +477 -0
  15. kailash/middleware/auth/kailash_jwt_auth.py +616 -0
  16. kailash/middleware/communication/__init__.py +37 -0
  17. kailash/middleware/communication/ai_chat.py +989 -0
  18. kailash/middleware/communication/api_gateway.py +802 -0
  19. kailash/middleware/communication/events.py +470 -0
  20. kailash/middleware/communication/realtime.py +710 -0
  21. kailash/middleware/core/__init__.py +21 -0
  22. kailash/middleware/core/agent_ui.py +890 -0
  23. kailash/middleware/core/schema.py +643 -0
  24. kailash/middleware/core/workflows.py +396 -0
  25. kailash/middleware/database/__init__.py +63 -0
  26. kailash/middleware/database/base.py +113 -0
  27. kailash/middleware/database/base_models.py +525 -0
  28. kailash/middleware/database/enums.py +106 -0
  29. kailash/middleware/database/migrations.py +12 -0
  30. kailash/{api/database.py → middleware/database/models.py} +183 -291
  31. kailash/middleware/database/repositories.py +685 -0
  32. kailash/middleware/database/session_manager.py +19 -0
  33. kailash/middleware/mcp/__init__.py +38 -0
  34. kailash/middleware/mcp/client_integration.py +585 -0
  35. kailash/middleware/mcp/enhanced_server.py +576 -0
  36. kailash/nodes/__init__.py +25 -3
  37. kailash/nodes/admin/__init__.py +35 -0
  38. kailash/nodes/admin/audit_log.py +794 -0
  39. kailash/nodes/admin/permission_check.py +864 -0
  40. kailash/nodes/admin/role_management.py +823 -0
  41. kailash/nodes/admin/security_event.py +1519 -0
  42. kailash/nodes/admin/user_management.py +944 -0
  43. kailash/nodes/ai/a2a.py +24 -7
  44. kailash/nodes/ai/ai_providers.py +1 -0
  45. kailash/nodes/ai/embedding_generator.py +11 -11
  46. kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
  47. kailash/nodes/ai/llm_agent.py +407 -2
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/api/auth.py +287 -6
  50. kailash/nodes/api/rest.py +151 -0
  51. kailash/nodes/auth/__init__.py +17 -0
  52. kailash/nodes/auth/directory_integration.py +1228 -0
  53. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  54. kailash/nodes/auth/mfa.py +2338 -0
  55. kailash/nodes/auth/risk_assessment.py +872 -0
  56. kailash/nodes/auth/session_management.py +1093 -0
  57. kailash/nodes/auth/sso.py +1040 -0
  58. kailash/nodes/base.py +344 -13
  59. kailash/nodes/base_cycle_aware.py +4 -2
  60. kailash/nodes/base_with_acl.py +1 -1
  61. kailash/nodes/code/python.py +283 -10
  62. kailash/nodes/compliance/__init__.py +9 -0
  63. kailash/nodes/compliance/data_retention.py +1888 -0
  64. kailash/nodes/compliance/gdpr.py +2004 -0
  65. kailash/nodes/data/__init__.py +22 -2
  66. kailash/nodes/data/async_connection.py +469 -0
  67. kailash/nodes/data/async_sql.py +757 -0
  68. kailash/nodes/data/async_vector.py +598 -0
  69. kailash/nodes/data/readers.py +767 -0
  70. kailash/nodes/data/retrieval.py +360 -1
  71. kailash/nodes/data/sharepoint_graph.py +397 -21
  72. kailash/nodes/data/sql.py +94 -5
  73. kailash/nodes/data/streaming.py +68 -8
  74. kailash/nodes/data/vector_db.py +54 -4
  75. kailash/nodes/enterprise/__init__.py +13 -0
  76. kailash/nodes/enterprise/batch_processor.py +741 -0
  77. kailash/nodes/enterprise/data_lineage.py +497 -0
  78. kailash/nodes/logic/convergence.py +31 -9
  79. kailash/nodes/logic/operations.py +14 -3
  80. kailash/nodes/mixins/__init__.py +8 -0
  81. kailash/nodes/mixins/event_emitter.py +201 -0
  82. kailash/nodes/mixins/mcp.py +9 -4
  83. kailash/nodes/mixins/security.py +165 -0
  84. kailash/nodes/monitoring/__init__.py +7 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  86. kailash/nodes/rag/__init__.py +284 -0
  87. kailash/nodes/rag/advanced.py +1615 -0
  88. kailash/nodes/rag/agentic.py +773 -0
  89. kailash/nodes/rag/conversational.py +999 -0
  90. kailash/nodes/rag/evaluation.py +875 -0
  91. kailash/nodes/rag/federated.py +1188 -0
  92. kailash/nodes/rag/graph.py +721 -0
  93. kailash/nodes/rag/multimodal.py +671 -0
  94. kailash/nodes/rag/optimized.py +933 -0
  95. kailash/nodes/rag/privacy.py +1059 -0
  96. kailash/nodes/rag/query_processing.py +1335 -0
  97. kailash/nodes/rag/realtime.py +764 -0
  98. kailash/nodes/rag/registry.py +547 -0
  99. kailash/nodes/rag/router.py +837 -0
  100. kailash/nodes/rag/similarity.py +1854 -0
  101. kailash/nodes/rag/strategies.py +566 -0
  102. kailash/nodes/rag/workflows.py +575 -0
  103. kailash/nodes/security/__init__.py +19 -0
  104. kailash/nodes/security/abac_evaluator.py +1411 -0
  105. kailash/nodes/security/audit_log.py +91 -0
  106. kailash/nodes/security/behavior_analysis.py +1893 -0
  107. kailash/nodes/security/credential_manager.py +401 -0
  108. kailash/nodes/security/rotating_credentials.py +760 -0
  109. kailash/nodes/security/security_event.py +132 -0
  110. kailash/nodes/security/threat_detection.py +1103 -0
  111. kailash/nodes/testing/__init__.py +9 -0
  112. kailash/nodes/testing/credential_testing.py +499 -0
  113. kailash/nodes/transform/__init__.py +10 -2
  114. kailash/nodes/transform/chunkers.py +592 -1
  115. kailash/nodes/transform/processors.py +484 -14
  116. kailash/nodes/validation.py +321 -0
  117. kailash/runtime/access_controlled.py +1 -1
  118. kailash/runtime/async_local.py +41 -7
  119. kailash/runtime/docker.py +1 -1
  120. kailash/runtime/local.py +474 -55
  121. kailash/runtime/parallel.py +1 -1
  122. kailash/runtime/parallel_cyclic.py +1 -1
  123. kailash/runtime/testing.py +210 -2
  124. kailash/utils/migrations/__init__.py +25 -0
  125. kailash/utils/migrations/generator.py +433 -0
  126. kailash/utils/migrations/models.py +231 -0
  127. kailash/utils/migrations/runner.py +489 -0
  128. kailash/utils/secure_logging.py +342 -0
  129. kailash/workflow/__init__.py +16 -0
  130. kailash/workflow/cyclic_runner.py +3 -4
  131. kailash/workflow/graph.py +70 -2
  132. kailash/workflow/resilience.py +249 -0
  133. kailash/workflow/templates.py +726 -0
  134. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
  135. kailash-0.4.0.dist-info/RECORD +223 -0
  136. kailash/api/__init__.py +0 -17
  137. kailash/api/__main__.py +0 -6
  138. kailash/api/studio_secure.py +0 -893
  139. kailash/mcp/__main__.py +0 -13
  140. kailash/mcp/server_new.py +0 -336
  141. kailash/mcp/servers/__init__.py +0 -12
  142. kailash-0.3.2.dist-info/RECORD +0 -136
  143. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
  144. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
  145. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
  146. {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1093 @@
1
+ """
2
+ Advanced session management with security tracking.
3
+
4
+ This module provides comprehensive session management capabilities including
5
+ concurrent session limits, device tracking, anomaly detection, and automatic
6
+ session cleanup with security event integration.
7
+ """
8
+
9
+ import hashlib
10
+ import json
11
+ import logging
12
+ import secrets
13
+ import threading
14
+ from dataclasses import dataclass
15
+ from datetime import UTC, datetime, timedelta
16
+ from enum import Enum
17
+ from typing import Any, Dict, List, Optional, Set
18
+
19
+ from kailash.nodes.base import Node, NodeParameter, register_node
20
+ from kailash.nodes.mixins import LoggingMixin, PerformanceMixin, SecurityMixin
21
+ from kailash.nodes.security.audit_log import AuditLogNode
22
+ from kailash.nodes.security.security_event import SecurityEventNode
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class SessionStatus(Enum):
28
+ """Session status enumeration."""
29
+
30
+ ACTIVE = "active"
31
+ IDLE = "idle"
32
+ EXPIRED = "expired"
33
+ TERMINATED = "terminated"
34
+ SUSPICIOUS = "suspicious"
35
+
36
+
37
+ @dataclass
38
+ class DeviceInfo:
39
+ """Device information for session tracking."""
40
+
41
+ device_id: str
42
+ device_type: str # desktop, mobile, tablet
43
+ os_name: str
44
+ os_version: str
45
+ browser_name: str
46
+ browser_version: str
47
+ user_agent: str
48
+ fingerprint: str
49
+
50
+
51
+ @dataclass
52
+ class SessionData:
53
+ """Complete session data."""
54
+
55
+ session_id: str
56
+ user_id: str
57
+ created_at: datetime
58
+ last_activity: datetime
59
+ expires_at: datetime
60
+ ip_address: str
61
+ device_info: DeviceInfo
62
+ status: SessionStatus
63
+
64
+ # Security tracking
65
+ login_method: str # password, mfa, sso
66
+ risk_score: float
67
+ anomaly_flags: List[str]
68
+
69
+ # Activity tracking
70
+ page_views: int
71
+ actions_performed: int
72
+ data_accessed_mb: float
73
+
74
+ # Geo-location (if available)
75
+ country: Optional[str] = None
76
+ city: Optional[str] = None
77
+
78
+ # Session metadata
79
+ metadata: Dict[str, Any] = None
80
+
81
+
82
+ @register_node()
83
+ class SessionManagementNode(SecurityMixin, PerformanceMixin, LoggingMixin, Node):
84
+ """Advanced session management with security tracking.
85
+
86
+ This node provides comprehensive session management including:
87
+ - Concurrent session limits per user
88
+ - Device fingerprinting and tracking
89
+ - Idle and absolute session timeouts
90
+ - Anomaly detection for sessions
91
+ - Geographic location tracking
92
+ - Session hijacking detection
93
+ - Automatic cleanup and security logging
94
+
95
+ Example:
96
+ >>> session_mgr = SessionManagementNode(
97
+ ... max_sessions=3,
98
+ ... idle_timeout=timedelta(minutes=30),
99
+ ... absolute_timeout=timedelta(hours=8),
100
+ ... track_devices=True
101
+ ... )
102
+ >>>
103
+ >>> # Create new session
104
+ >>> device_info = {
105
+ ... "device_type": "desktop",
106
+ ... "os_name": "Windows",
107
+ ... "browser_name": "Chrome",
108
+ ... "user_agent": "Mozilla/5.0..."
109
+ ... }
110
+ >>>
111
+ >>> result = session_mgr.run(
112
+ ... action="create",
113
+ ... user_id="user123",
114
+ ... ip_address="192.168.1.100",
115
+ ... device_info=device_info
116
+ ... )
117
+ >>> print(f"Session ID: {result['session_id']}")
118
+ >>>
119
+ >>> # Validate session
120
+ >>> validation = session_mgr.run(
121
+ ... action="validate",
122
+ ... session_id=result['session_id']
123
+ ... )
124
+ >>> print(f"Valid: {validation['valid']}")
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ name: str = "session_management",
130
+ max_sessions: int = 3,
131
+ idle_timeout: timedelta = timedelta(minutes=30),
132
+ absolute_timeout: timedelta = timedelta(hours=8),
133
+ track_devices: bool = True,
134
+ enable_geo_tracking: bool = False,
135
+ anomaly_detection: bool = True,
136
+ cleanup_interval: int = 300, # 5 minutes
137
+ **kwargs,
138
+ ):
139
+ """Initialize session management node.
140
+
141
+ Args:
142
+ name: Node name
143
+ max_sessions: Maximum concurrent sessions per user
144
+ idle_timeout: Idle session timeout
145
+ absolute_timeout: Absolute session timeout
146
+ track_devices: Enable device tracking and fingerprinting
147
+ enable_geo_tracking: Enable geographic location tracking
148
+ anomaly_detection: Enable session anomaly detection
149
+ cleanup_interval: Cleanup interval in seconds
150
+ **kwargs: Additional node parameters
151
+ """
152
+ # Set attributes before calling super().__init__()
153
+ self.max_sessions = max_sessions
154
+ self.idle_timeout = idle_timeout
155
+ self.absolute_timeout = absolute_timeout
156
+ self.track_devices = track_devices
157
+ self.enable_geo_tracking = enable_geo_tracking
158
+ self.anomaly_detection = anomaly_detection
159
+ self.cleanup_interval = cleanup_interval
160
+
161
+ # Initialize parent classes
162
+ super().__init__(name=name, **kwargs)
163
+
164
+ # Initialize audit logging and security events
165
+ self.audit_log_node = AuditLogNode(name=f"{name}_audit_log")
166
+ self.security_event_node = SecurityEventNode(name=f"{name}_security_events")
167
+
168
+ # Session storage
169
+ self.sessions: Dict[str, SessionData] = {}
170
+ self.user_sessions: Dict[str, Set[str]] = {} # user_id -> set of session_ids
171
+ self.device_sessions: Dict[str, Set[str]] = (
172
+ {}
173
+ ) # device_fingerprint -> set of session_ids
174
+
175
+ # Thread locks for concurrent access
176
+ self._sessions_lock = threading.Lock()
177
+
178
+ # Session statistics
179
+ self.session_stats = {
180
+ "total_sessions_created": 0,
181
+ "active_sessions": 0,
182
+ "expired_sessions_cleaned": 0,
183
+ "concurrent_limit_hits": 0,
184
+ "anomalies_detected": 0,
185
+ "devices_tracked": 0,
186
+ "sessions_terminated": 0,
187
+ }
188
+
189
+ # Last cleanup time
190
+ self._last_cleanup = datetime.now(UTC)
191
+
192
+ def get_parameters(self) -> Dict[str, NodeParameter]:
193
+ """Get node parameters for validation and documentation.
194
+
195
+ Returns:
196
+ Dictionary mapping parameter names to NodeParameter objects
197
+ """
198
+ return {
199
+ "action": NodeParameter(
200
+ name="action",
201
+ type=str,
202
+ description="Session action to perform",
203
+ required=True,
204
+ ),
205
+ "session_id": NodeParameter(
206
+ name="session_id",
207
+ type=str,
208
+ description="Session ID for operations",
209
+ required=False,
210
+ ),
211
+ "user_id": NodeParameter(
212
+ name="user_id",
213
+ type=str,
214
+ description="User ID for session operations",
215
+ required=False,
216
+ ),
217
+ "ip_address": NodeParameter(
218
+ name="ip_address",
219
+ type=str,
220
+ description="Client IP address",
221
+ required=False,
222
+ ),
223
+ "device_info": NodeParameter(
224
+ name="device_info",
225
+ type=dict,
226
+ description="Device information for tracking",
227
+ required=False,
228
+ default={},
229
+ ),
230
+ }
231
+
232
+ def run(
233
+ self,
234
+ action: str,
235
+ session_id: Optional[str] = None,
236
+ user_id: Optional[str] = None,
237
+ ip_address: Optional[str] = None,
238
+ device_info: Optional[Dict[str, Any]] = None,
239
+ **kwargs,
240
+ ) -> Dict[str, Any]:
241
+ """Run session management operation.
242
+
243
+ Args:
244
+ action: Session action (create, validate, update, terminate, cleanup)
245
+ session_id: Session ID for operations
246
+ user_id: User ID for session operations
247
+ ip_address: Client IP address
248
+ device_info: Device information
249
+ **kwargs: Additional parameters
250
+
251
+ Returns:
252
+ Dictionary containing operation results
253
+ """
254
+ start_time = datetime.now(UTC)
255
+ device_info = device_info or {}
256
+
257
+ try:
258
+ # Validate and sanitize inputs
259
+ safe_params = self.validate_and_sanitize_inputs(
260
+ {
261
+ "action": action,
262
+ "session_id": session_id or "",
263
+ "user_id": user_id or "",
264
+ "ip_address": ip_address or "",
265
+ "device_info": device_info,
266
+ }
267
+ )
268
+
269
+ action = safe_params["action"]
270
+ session_id = safe_params["session_id"] or None
271
+ user_id = safe_params["user_id"] or None
272
+ ip_address = safe_params["ip_address"] or None
273
+ device_info = safe_params["device_info"]
274
+
275
+ self.log_node_execution("session_operation_start", action=action)
276
+
277
+ # Perform periodic cleanup
278
+ self._maybe_cleanup_sessions()
279
+
280
+ # Route to appropriate action handler
281
+ if action == "create":
282
+ if not user_id or not ip_address:
283
+ return {
284
+ "success": False,
285
+ "error": "user_id and ip_address required for create",
286
+ }
287
+ result = self._create_session(user_id, ip_address, device_info)
288
+ self.session_stats["total_sessions_created"] += 1
289
+
290
+ elif action == "validate":
291
+ if not session_id:
292
+ return {
293
+ "success": False,
294
+ "error": "session_id required for validate",
295
+ }
296
+ result = self._validate_session(session_id)
297
+
298
+ elif action == "update":
299
+ if not session_id:
300
+ return {"success": False, "error": "session_id required for update"}
301
+ result = self._update_session_activity(session_id, kwargs)
302
+
303
+ elif action == "terminate":
304
+ if session_id:
305
+ result = self._terminate_session(
306
+ session_id, kwargs.get("reason", "user_logout")
307
+ )
308
+ elif user_id:
309
+ result = self._terminate_user_sessions(
310
+ user_id, kwargs.get("reason", "admin_action")
311
+ )
312
+ else:
313
+ return {
314
+ "success": False,
315
+ "error": "session_id or user_id required for terminate",
316
+ }
317
+
318
+ elif action == "cleanup":
319
+ result = self._cleanup_expired_sessions()
320
+
321
+ elif action == "list":
322
+ if not user_id:
323
+ return {"success": False, "error": "user_id required for list"}
324
+ result = self._list_user_sessions(user_id)
325
+
326
+ elif action == "stats":
327
+ result = self._get_session_statistics()
328
+
329
+ else:
330
+ result = {"success": False, "error": f"Unknown action: {action}"}
331
+
332
+ # Add timing information
333
+ processing_time = (datetime.now(UTC) - start_time).total_seconds() * 1000
334
+ result["processing_time_ms"] = processing_time
335
+ result["timestamp"] = start_time.isoformat()
336
+
337
+ self.log_node_execution(
338
+ "session_operation_complete",
339
+ action=action,
340
+ success=result.get("success", False),
341
+ processing_time_ms=processing_time,
342
+ )
343
+
344
+ return result
345
+
346
+ except Exception as e:
347
+ self.log_error_with_traceback(e, "session_management")
348
+ raise
349
+
350
+ def _create_session(
351
+ self, user_id: str, ip_address: str, device_info: Dict[str, Any]
352
+ ) -> Dict[str, Any]:
353
+ """Create new user session.
354
+
355
+ Args:
356
+ user_id: User ID
357
+ ip_address: Client IP address
358
+ device_info: Device information
359
+
360
+ Returns:
361
+ Session creation result
362
+ """
363
+ with self._sessions_lock:
364
+ # Check concurrent session limit
365
+ user_session_count = len(self.user_sessions.get(user_id, set()))
366
+ if user_session_count >= self.max_sessions:
367
+ self.session_stats["concurrent_limit_hits"] += 1
368
+
369
+ # Terminate oldest session
370
+ oldest_session = self._get_oldest_user_session(user_id)
371
+ if oldest_session:
372
+ self._terminate_session_internal(
373
+ oldest_session, "concurrent_limit_exceeded"
374
+ )
375
+ self._log_security_event(
376
+ user_id,
377
+ "session_limit_exceeded",
378
+ "medium",
379
+ {"max_sessions": self.max_sessions},
380
+ )
381
+
382
+ # Generate session ID
383
+ session_id = self._generate_session_id()
384
+
385
+ # Process device information
386
+ device = self._process_device_info(device_info, ip_address)
387
+
388
+ # Calculate session risk score
389
+ risk_score = self._calculate_session_risk(user_id, ip_address, device)
390
+
391
+ # Create session data
392
+ current_time = datetime.now(UTC)
393
+ session_data = SessionData(
394
+ session_id=session_id,
395
+ user_id=user_id,
396
+ created_at=current_time,
397
+ last_activity=current_time,
398
+ expires_at=current_time + self.absolute_timeout,
399
+ ip_address=ip_address,
400
+ device_info=device,
401
+ status=SessionStatus.ACTIVE,
402
+ login_method="password", # Default, should be set by caller
403
+ risk_score=risk_score,
404
+ anomaly_flags=[],
405
+ page_views=0,
406
+ actions_performed=0,
407
+ data_accessed_mb=0.0,
408
+ metadata={},
409
+ )
410
+
411
+ # Add geo-location if enabled
412
+ if self.enable_geo_tracking:
413
+ location = self._get_ip_location(ip_address)
414
+ session_data.country = location.get("country")
415
+ session_data.city = location.get("city")
416
+
417
+ # Store session
418
+ self.sessions[session_id] = session_data
419
+
420
+ # Update user sessions mapping
421
+ if user_id not in self.user_sessions:
422
+ self.user_sessions[user_id] = set()
423
+ self.user_sessions[user_id].add(session_id)
424
+
425
+ # Update device sessions mapping if tracking enabled
426
+ if self.track_devices:
427
+ device_fingerprint = device.fingerprint
428
+ if device_fingerprint not in self.device_sessions:
429
+ self.device_sessions[device_fingerprint] = set()
430
+ self.session_stats["devices_tracked"] += 1
431
+ self.device_sessions[device_fingerprint].add(session_id)
432
+
433
+ # Update statistics
434
+ self.session_stats["active_sessions"] += 1
435
+
436
+ # Audit log session creation
437
+ self._audit_session_operation("create", session_data)
438
+
439
+ # Check for anomalies
440
+ if self.anomaly_detection:
441
+ anomalies = self._detect_session_anomalies(session_data)
442
+ if anomalies:
443
+ session_data.anomaly_flags.extend(anomalies)
444
+ self.session_stats["anomalies_detected"] += len(anomalies)
445
+ self._log_security_event(
446
+ user_id,
447
+ "session_anomaly_detected",
448
+ "medium",
449
+ {"anomalies": anomalies, "session_id": session_id},
450
+ )
451
+
452
+ return {
453
+ "success": True,
454
+ "session_id": session_id,
455
+ "expires_at": session_data.expires_at.isoformat(),
456
+ "risk_score": risk_score,
457
+ "device_tracked": self.track_devices,
458
+ "anomalies": session_data.anomaly_flags,
459
+ "concurrent_sessions": len(self.user_sessions[user_id]),
460
+ }
461
+
462
+ def _validate_session(self, session_id: str) -> Dict[str, Any]:
463
+ """Validate session and check for anomalies.
464
+
465
+ Args:
466
+ session_id: Session ID to validate
467
+
468
+ Returns:
469
+ Session validation result
470
+ """
471
+ with self._sessions_lock:
472
+ if session_id not in self.sessions:
473
+ return {"success": True, "valid": False, "reason": "session_not_found"}
474
+
475
+ session_data = self.sessions[session_id]
476
+ current_time = datetime.now(UTC)
477
+
478
+ # Check if session is expired
479
+ if current_time > session_data.expires_at:
480
+ session_data.status = SessionStatus.EXPIRED
481
+ self._cleanup_session_internal(session_id)
482
+ return {"success": True, "valid": False, "reason": "session_expired"}
483
+
484
+ # Check idle timeout
485
+ idle_time = current_time - session_data.last_activity
486
+ if idle_time > self.idle_timeout:
487
+ session_data.status = SessionStatus.IDLE
488
+ if idle_time > self.idle_timeout * 2: # Grace period
489
+ self._cleanup_session_internal(session_id)
490
+ return {
491
+ "success": True,
492
+ "valid": False,
493
+ "reason": "session_idle_timeout",
494
+ }
495
+
496
+ # Check for suspicious activity
497
+ if session_data.status == SessionStatus.SUSPICIOUS:
498
+ return {"success": True, "valid": False, "reason": "session_suspicious"}
499
+
500
+ # Session is valid
501
+ return {
502
+ "success": True,
503
+ "valid": True,
504
+ "session_data": {
505
+ "user_id": session_data.user_id,
506
+ "created_at": session_data.created_at.isoformat(),
507
+ "last_activity": session_data.last_activity.isoformat(),
508
+ "expires_at": session_data.expires_at.isoformat(),
509
+ "status": session_data.status.value,
510
+ "risk_score": session_data.risk_score,
511
+ "anomaly_flags": session_data.anomaly_flags,
512
+ "device_type": session_data.device_info.device_type,
513
+ "location": (
514
+ f"{session_data.city}, {session_data.country}"
515
+ if session_data.city
516
+ else None
517
+ ),
518
+ },
519
+ }
520
+
521
+ def _update_session_activity(
522
+ self, session_id: str, activity_data: Dict[str, Any]
523
+ ) -> Dict[str, Any]:
524
+ """Update session activity and check for anomalies.
525
+
526
+ Args:
527
+ session_id: Session ID
528
+ activity_data: Activity data to record
529
+
530
+ Returns:
531
+ Update result
532
+ """
533
+ with self._sessions_lock:
534
+ if session_id not in self.sessions:
535
+ return {"success": False, "error": "session_not_found"}
536
+
537
+ session_data = self.sessions[session_id]
538
+ current_time = datetime.now(UTC)
539
+
540
+ # Update last activity
541
+ session_data.last_activity = current_time
542
+ session_data.status = SessionStatus.ACTIVE
543
+
544
+ # Update activity counters
545
+ if "page_views" in activity_data:
546
+ session_data.page_views += activity_data["page_views"]
547
+
548
+ if "actions_performed" in activity_data:
549
+ session_data.actions_performed += activity_data["actions_performed"]
550
+
551
+ if "data_accessed_mb" in activity_data:
552
+ session_data.data_accessed_mb += activity_data["data_accessed_mb"]
553
+
554
+ # Check for new anomalies
555
+ if self.anomaly_detection:
556
+ new_anomalies = self._detect_activity_anomalies(
557
+ session_data, activity_data
558
+ )
559
+ if new_anomalies:
560
+ session_data.anomaly_flags.extend(new_anomalies)
561
+ self.session_stats["anomalies_detected"] += len(new_anomalies)
562
+
563
+ # Mark session as suspicious if too many anomalies
564
+ if len(session_data.anomaly_flags) > 3:
565
+ session_data.status = SessionStatus.SUSPICIOUS
566
+ self._log_security_event(
567
+ session_data.user_id,
568
+ "session_marked_suspicious",
569
+ "high",
570
+ {
571
+ "session_id": session_id,
572
+ "anomalies": session_data.anomaly_flags,
573
+ },
574
+ )
575
+
576
+ return {
577
+ "success": True,
578
+ "session_updated": True,
579
+ "status": session_data.status.value,
580
+ "new_anomalies": new_anomalies if self.anomaly_detection else [],
581
+ "total_anomalies": len(session_data.anomaly_flags),
582
+ }
583
+
584
+ def _terminate_session(
585
+ self, session_id: str, reason: str = "user_logout"
586
+ ) -> Dict[str, Any]:
587
+ """Terminate user session.
588
+
589
+ Args:
590
+ session_id: Session ID to terminate
591
+ reason: Termination reason
592
+
593
+ Returns:
594
+ Termination result
595
+ """
596
+ with self._sessions_lock:
597
+ if session_id not in self.sessions:
598
+ return {"success": False, "error": "session_not_found"}
599
+
600
+ session_data = self.sessions[session_id]
601
+ self._terminate_session_internal(session_id, reason)
602
+
603
+ # Audit log termination
604
+ self._audit_session_operation("terminate", session_data, {"reason": reason})
605
+
606
+ return {
607
+ "success": True,
608
+ "session_terminated": True,
609
+ "reason": reason,
610
+ "user_id": session_data.user_id,
611
+ }
612
+
613
+ def _terminate_user_sessions(
614
+ self, user_id: str, reason: str = "admin_action"
615
+ ) -> Dict[str, Any]:
616
+ """Terminate all sessions for a user.
617
+
618
+ Args:
619
+ user_id: User ID
620
+ reason: Termination reason
621
+
622
+ Returns:
623
+ Termination result
624
+ """
625
+ with self._sessions_lock:
626
+ if user_id not in self.user_sessions:
627
+ return {"success": True, "sessions_terminated": 0}
628
+
629
+ session_ids = list(self.user_sessions[user_id])
630
+ terminated_count = 0
631
+
632
+ for session_id in session_ids:
633
+ if session_id in self.sessions:
634
+ self._terminate_session_internal(session_id, reason)
635
+ terminated_count += 1
636
+
637
+ # Log security event for mass termination
638
+ if terminated_count > 1:
639
+ self._log_security_event(
640
+ user_id,
641
+ "mass_session_termination",
642
+ "medium",
643
+ {"sessions_terminated": terminated_count, "reason": reason},
644
+ )
645
+
646
+ return {
647
+ "success": True,
648
+ "sessions_terminated": terminated_count,
649
+ "reason": reason,
650
+ }
651
+
652
+ def _terminate_session_internal(self, session_id: str, reason: str) -> None:
653
+ """Internal session termination.
654
+
655
+ Args:
656
+ session_id: Session ID
657
+ reason: Termination reason
658
+ """
659
+ if session_id not in self.sessions:
660
+ return
661
+
662
+ session_data = self.sessions[session_id]
663
+
664
+ # Update status
665
+ session_data.status = SessionStatus.TERMINATED
666
+
667
+ # Remove from active sessions
668
+ self._cleanup_session_internal(session_id)
669
+
670
+ # Update statistics
671
+ self.session_stats["sessions_terminated"] += 1
672
+
673
+ def _cleanup_session_internal(self, session_id: str) -> None:
674
+ """Internal session cleanup.
675
+
676
+ Args:
677
+ session_id: Session ID to cleanup
678
+ """
679
+ if session_id not in self.sessions:
680
+ return
681
+
682
+ session_data = self.sessions[session_id]
683
+
684
+ # Remove from user sessions
685
+ if session_data.user_id in self.user_sessions:
686
+ self.user_sessions[session_data.user_id].discard(session_id)
687
+ if not self.user_sessions[session_data.user_id]:
688
+ del self.user_sessions[session_data.user_id]
689
+
690
+ # Remove from device sessions
691
+ if self.track_devices:
692
+ device_fingerprint = session_data.device_info.fingerprint
693
+ if device_fingerprint in self.device_sessions:
694
+ self.device_sessions[device_fingerprint].discard(session_id)
695
+ if not self.device_sessions[device_fingerprint]:
696
+ del self.device_sessions[device_fingerprint]
697
+
698
+ # Remove session
699
+ del self.sessions[session_id]
700
+
701
+ # Update statistics
702
+ if self.session_stats["active_sessions"] > 0:
703
+ self.session_stats["active_sessions"] -= 1
704
+
705
+ def _cleanup_expired_sessions(self) -> Dict[str, Any]:
706
+ """Clean up expired and idle sessions.
707
+
708
+ Returns:
709
+ Cleanup result
710
+ """
711
+ current_time = datetime.now(UTC)
712
+ expired_sessions = []
713
+ idle_sessions = []
714
+
715
+ with self._sessions_lock:
716
+ for session_id, session_data in list(self.sessions.items()):
717
+ # Check for expired sessions
718
+ if current_time > session_data.expires_at:
719
+ expired_sessions.append(session_id)
720
+ continue
721
+
722
+ # Check for idle sessions beyond grace period
723
+ idle_time = current_time - session_data.last_activity
724
+ if idle_time > self.idle_timeout * 2: # Grace period
725
+ idle_sessions.append(session_id)
726
+
727
+ # Clean up expired and idle sessions
728
+ for session_id in expired_sessions + idle_sessions:
729
+ self._cleanup_session_internal(session_id)
730
+
731
+ # Update statistics
732
+ total_cleaned = len(expired_sessions) + len(idle_sessions)
733
+ self.session_stats["expired_sessions_cleaned"] += total_cleaned
734
+
735
+ # Update last cleanup time
736
+ self._last_cleanup = current_time
737
+
738
+ return {
739
+ "success": True,
740
+ "expired_sessions_cleaned": len(expired_sessions),
741
+ "idle_sessions_cleaned": len(idle_sessions),
742
+ "total_cleaned": total_cleaned,
743
+ }
744
+
745
+ def _maybe_cleanup_sessions(self) -> None:
746
+ """Perform cleanup if enough time has passed."""
747
+ current_time = datetime.now(UTC)
748
+ if (current_time - self._last_cleanup).total_seconds() > self.cleanup_interval:
749
+ self._cleanup_expired_sessions()
750
+
751
+ def _list_user_sessions(self, user_id: str) -> Dict[str, Any]:
752
+ """List all sessions for a user.
753
+
754
+ Args:
755
+ user_id: User ID
756
+
757
+ Returns:
758
+ List of user sessions
759
+ """
760
+ with self._sessions_lock:
761
+ if user_id not in self.user_sessions:
762
+ return {"success": True, "sessions": []}
763
+
764
+ sessions_list = []
765
+ for session_id in self.user_sessions[user_id]:
766
+ if session_id in self.sessions:
767
+ session_data = self.sessions[session_id]
768
+ sessions_list.append(
769
+ {
770
+ "session_id": session_id,
771
+ "created_at": session_data.created_at.isoformat(),
772
+ "last_activity": session_data.last_activity.isoformat(),
773
+ "status": session_data.status.value,
774
+ "ip_address": session_data.ip_address,
775
+ "device_type": session_data.device_info.device_type,
776
+ "device_os": f"{session_data.device_info.os_name} {session_data.device_info.os_version}",
777
+ "browser": f"{session_data.device_info.browser_name} {session_data.device_info.browser_version}",
778
+ "location": (
779
+ f"{session_data.city}, {session_data.country}"
780
+ if session_data.city
781
+ else None
782
+ ),
783
+ "risk_score": session_data.risk_score,
784
+ "anomaly_count": len(session_data.anomaly_flags),
785
+ }
786
+ )
787
+
788
+ return {
789
+ "success": True,
790
+ "sessions": sessions_list,
791
+ "total_sessions": len(sessions_list),
792
+ }
793
+
794
+ def _get_session_statistics(self) -> Dict[str, Any]:
795
+ """Get session management statistics.
796
+
797
+ Returns:
798
+ Session statistics
799
+ """
800
+ with self._sessions_lock:
801
+ # Calculate additional statistics
802
+ device_count = len(self.device_sessions) if self.track_devices else 0
803
+ user_count = len(self.user_sessions)
804
+
805
+ # Session status distribution
806
+ status_distribution = {}
807
+ for session_data in self.sessions.values():
808
+ status = session_data.status.value
809
+ status_distribution[status] = status_distribution.get(status, 0) + 1
810
+
811
+ return {
812
+ "success": True,
813
+ "statistics": {
814
+ **self.session_stats,
815
+ "current_active_sessions": len(self.sessions),
816
+ "unique_users": user_count,
817
+ "unique_devices": device_count,
818
+ "status_distribution": status_distribution,
819
+ "max_sessions_per_user": self.max_sessions,
820
+ "idle_timeout_minutes": self.idle_timeout.total_seconds() / 60,
821
+ "absolute_timeout_hours": self.absolute_timeout.total_seconds()
822
+ / 3600,
823
+ },
824
+ }
825
+
826
+ def _generate_session_id(self) -> str:
827
+ """Generate secure session ID.
828
+
829
+ Returns:
830
+ Session ID
831
+ """
832
+ return secrets.token_urlsafe(32)
833
+
834
+ def _process_device_info(
835
+ self, device_info: Dict[str, Any], ip_address: str
836
+ ) -> DeviceInfo:
837
+ """Process and create device information.
838
+
839
+ Args:
840
+ device_info: Raw device information
841
+ ip_address: Client IP address
842
+
843
+ Returns:
844
+ Processed device information
845
+ """
846
+ # Extract device information with defaults
847
+ device_type = device_info.get("device_type", "unknown")
848
+ os_name = device_info.get("os_name", "unknown")
849
+ os_version = device_info.get("os_version", "unknown")
850
+ browser_name = device_info.get("browser_name", "unknown")
851
+ browser_version = device_info.get("browser_version", "unknown")
852
+ user_agent = device_info.get("user_agent", "unknown")
853
+
854
+ # Generate device fingerprint
855
+ fingerprint_data = f"{device_type}:{os_name}:{os_version}:{browser_name}:{browser_version}:{user_agent}"
856
+ fingerprint = hashlib.sha256(fingerprint_data.encode()).hexdigest()[:16]
857
+
858
+ # Create device ID (more stable than fingerprint)
859
+ device_id = device_info.get("device_id", fingerprint)
860
+
861
+ return DeviceInfo(
862
+ device_id=device_id,
863
+ device_type=device_type,
864
+ os_name=os_name,
865
+ os_version=os_version,
866
+ browser_name=browser_name,
867
+ browser_version=browser_version,
868
+ user_agent=user_agent,
869
+ fingerprint=fingerprint,
870
+ )
871
+
872
+ def _calculate_session_risk(
873
+ self, user_id: str, ip_address: str, device: DeviceInfo
874
+ ) -> float:
875
+ """Calculate session risk score.
876
+
877
+ Args:
878
+ user_id: User ID
879
+ ip_address: IP address
880
+ device: Device information
881
+
882
+ Returns:
883
+ Risk score (0-1)
884
+ """
885
+ risk_score = 0.0
886
+
887
+ # Check for new device
888
+ if self.track_devices and device.fingerprint not in self.device_sessions:
889
+ risk_score += 0.3
890
+
891
+ # Check for multiple active sessions
892
+ user_session_count = len(self.user_sessions.get(user_id, set()))
893
+ if user_session_count >= self.max_sessions - 1:
894
+ risk_score += 0.2
895
+
896
+ # Check for unusual device type
897
+ if device.device_type == "unknown":
898
+ risk_score += 0.2
899
+
900
+ # Check for mobile devices (potentially higher risk)
901
+ if device.device_type == "mobile":
902
+ risk_score += 0.1
903
+
904
+ # Geo-location checks would go here
905
+ # For now, add base risk for any session
906
+ risk_score += 0.1
907
+
908
+ return min(1.0, risk_score)
909
+
910
+ def _get_ip_location(self, ip_address: str) -> Dict[str, str]:
911
+ """Get geographic location for IP address.
912
+
913
+ Args:
914
+ ip_address: IP address
915
+
916
+ Returns:
917
+ Location information
918
+ """
919
+ # In a real implementation, this would use a geo-IP service
920
+ # For now, return mock data
921
+ return {"country": "Unknown", "city": "Unknown"}
922
+
923
+ def _detect_session_anomalies(self, session_data: SessionData) -> List[str]:
924
+ """Detect anomalies in session creation.
925
+
926
+ Args:
927
+ session_data: Session data to analyze
928
+
929
+ Returns:
930
+ List of anomaly indicators
931
+ """
932
+ anomalies = []
933
+
934
+ # Check for multiple concurrent sessions
935
+ user_session_count = len(self.user_sessions.get(session_data.user_id, set()))
936
+ if user_session_count >= self.max_sessions:
937
+ anomalies.append("max_concurrent_sessions")
938
+
939
+ # Check for new device
940
+ if (
941
+ self.track_devices
942
+ and session_data.device_info.fingerprint not in self.device_sessions
943
+ ):
944
+ anomalies.append("new_device")
945
+
946
+ # Check for unusual IP address
947
+ # In a real implementation, this would check against user's IP history
948
+ if session_data.ip_address.startswith(
949
+ "10."
950
+ ) or session_data.ip_address.startswith("192.168."):
951
+ # Internal IP - potentially lower risk
952
+ pass
953
+ else:
954
+ # External IP - check against known IPs
955
+ anomalies.append("external_ip")
956
+
957
+ # Check for high risk score
958
+ if session_data.risk_score > 0.7:
959
+ anomalies.append("high_risk_score")
960
+
961
+ return anomalies
962
+
963
+ def _detect_activity_anomalies(
964
+ self, session_data: SessionData, activity_data: Dict[str, Any]
965
+ ) -> List[str]:
966
+ """Detect anomalies in session activity.
967
+
968
+ Args:
969
+ session_data: Session data
970
+ activity_data: New activity data
971
+
972
+ Returns:
973
+ List of anomaly indicators
974
+ """
975
+ anomalies = []
976
+
977
+ # Check for excessive page views
978
+ new_page_views = activity_data.get("page_views", 0)
979
+ if new_page_views > 100: # More than 100 page views in one update
980
+ anomalies.append("excessive_page_views")
981
+
982
+ # Check for excessive actions
983
+ new_actions = activity_data.get("actions_performed", 0)
984
+ if new_actions > 500: # More than 500 actions in one update
985
+ anomalies.append("excessive_actions")
986
+
987
+ # Check for large data access
988
+ new_data_mb = activity_data.get("data_accessed_mb", 0)
989
+ if new_data_mb > 100: # More than 100MB in one update
990
+ anomalies.append("large_data_access")
991
+
992
+ # Check session duration vs activity
993
+ session_duration = (
994
+ datetime.now(UTC) - session_data.created_at
995
+ ).total_seconds() / 60
996
+ if session_duration < 5 and (new_page_views > 50 or new_actions > 100):
997
+ anomalies.append("rapid_activity")
998
+
999
+ return anomalies
1000
+
1001
+ def _get_oldest_user_session(self, user_id: str) -> Optional[str]:
1002
+ """Get oldest session for user.
1003
+
1004
+ Args:
1005
+ user_id: User ID
1006
+
1007
+ Returns:
1008
+ Oldest session ID or None
1009
+ """
1010
+ if user_id not in self.user_sessions:
1011
+ return None
1012
+
1013
+ oldest_session = None
1014
+ oldest_time = None
1015
+
1016
+ for session_id in self.user_sessions[user_id]:
1017
+ if session_id in self.sessions:
1018
+ session_data = self.sessions[session_id]
1019
+ if oldest_time is None or session_data.created_at < oldest_time:
1020
+ oldest_time = session_data.created_at
1021
+ oldest_session = session_id
1022
+
1023
+ return oldest_session
1024
+
1025
+ def _audit_session_operation(
1026
+ self,
1027
+ operation: str,
1028
+ session_data: SessionData,
1029
+ metadata: Optional[Dict[str, Any]] = None,
1030
+ ) -> None:
1031
+ """Audit session operation.
1032
+
1033
+ Args:
1034
+ operation: Operation performed
1035
+ session_data: Session data
1036
+ metadata: Additional metadata
1037
+ """
1038
+ audit_entry = {
1039
+ "action": f"session_{operation}",
1040
+ "user_id": session_data.user_id,
1041
+ "resource_type": "session",
1042
+ "resource_id": session_data.session_id,
1043
+ "metadata": {
1044
+ "operation": operation,
1045
+ "ip_address": session_data.ip_address,
1046
+ "device_type": session_data.device_info.device_type,
1047
+ "risk_score": session_data.risk_score,
1048
+ **(metadata or {}),
1049
+ },
1050
+ "ip_address": session_data.ip_address,
1051
+ }
1052
+
1053
+ try:
1054
+ self.audit_log_node.run(**audit_entry)
1055
+ except Exception as e:
1056
+ self.log_with_context("WARNING", f"Failed to audit session operation: {e}")
1057
+
1058
+ def _log_security_event(
1059
+ self, user_id: str, event_type: str, severity: str, metadata: Dict[str, Any]
1060
+ ) -> None:
1061
+ """Log security event.
1062
+
1063
+ Args:
1064
+ user_id: User ID
1065
+ event_type: Type of security event
1066
+ severity: Event severity
1067
+ metadata: Event metadata
1068
+ """
1069
+ security_event = {
1070
+ "event_type": event_type,
1071
+ "severity": severity,
1072
+ "description": f"Session management: {event_type}",
1073
+ "metadata": {"session_management": True, **metadata},
1074
+ "user_id": user_id,
1075
+ "source_ip": metadata.get("ip_address", "unknown"),
1076
+ }
1077
+
1078
+ try:
1079
+ self.security_event_node.run(**security_event)
1080
+ except Exception as e:
1081
+ self.log_with_context("WARNING", f"Failed to log security event: {e}")
1082
+
1083
+ def get_session_stats(self) -> Dict[str, Any]:
1084
+ """Get session management statistics.
1085
+
1086
+ Returns:
1087
+ Dictionary with session statistics
1088
+ """
1089
+ return self._get_session_statistics()["statistics"]
1090
+
1091
+ async def async_run(self, **kwargs) -> Dict[str, Any]:
1092
+ """Async execution method for enterprise integration."""
1093
+ return self.run(**kwargs)