kailash 0.6.0__py3-none-any.whl → 0.6.2__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.
- kailash/__init__.py +1 -1
- kailash/access_control/__init__.py +1 -1
- kailash/core/actors/adaptive_pool_controller.py +630 -0
- kailash/core/actors/connection_actor.py +3 -3
- kailash/core/ml/__init__.py +1 -0
- kailash/core/ml/query_patterns.py +544 -0
- kailash/core/monitoring/__init__.py +19 -0
- kailash/core/monitoring/connection_metrics.py +488 -0
- kailash/core/optimization/__init__.py +1 -0
- kailash/core/resilience/__init__.py +17 -0
- kailash/core/resilience/circuit_breaker.py +382 -0
- kailash/gateway/api.py +7 -5
- kailash/gateway/enhanced_gateway.py +1 -1
- kailash/middleware/auth/access_control.py +11 -11
- kailash/middleware/communication/ai_chat.py +7 -7
- kailash/middleware/communication/api_gateway.py +5 -15
- kailash/middleware/gateway/checkpoint_manager.py +45 -8
- kailash/middleware/gateway/event_store.py +66 -26
- kailash/middleware/mcp/enhanced_server.py +2 -2
- kailash/nodes/admin/permission_check.py +110 -30
- kailash/nodes/admin/schema.sql +387 -0
- kailash/nodes/admin/tenant_isolation.py +249 -0
- kailash/nodes/admin/transaction_utils.py +244 -0
- kailash/nodes/admin/user_management.py +37 -9
- kailash/nodes/ai/ai_providers.py +55 -3
- kailash/nodes/ai/llm_agent.py +115 -13
- kailash/nodes/data/query_pipeline.py +641 -0
- kailash/nodes/data/query_router.py +895 -0
- kailash/nodes/data/sql.py +24 -0
- kailash/nodes/data/workflow_connection_pool.py +451 -23
- kailash/nodes/monitoring/__init__.py +3 -5
- kailash/nodes/monitoring/connection_dashboard.py +822 -0
- kailash/nodes/rag/__init__.py +1 -3
- kailash/resources/registry.py +6 -0
- kailash/runtime/async_local.py +7 -0
- kailash/utils/export.py +152 -0
- kailash/workflow/builder.py +42 -0
- kailash/workflow/graph.py +86 -17
- kailash/workflow/templates.py +4 -9
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/METADATA +14 -1
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/RECORD +45 -31
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/WHEEL +0 -0
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/entry_points.txt +0 -0
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.6.0.dist-info → kailash-0.6.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,387 @@
|
|
1
|
+
-- Unified Admin Node Database Schema
|
2
|
+
-- Complete RBAC/ABAC system with user management and permission checking
|
3
|
+
-- Production-ready schema for Kailash Admin Nodes
|
4
|
+
|
5
|
+
-- =====================================================
|
6
|
+
-- Core User Management
|
7
|
+
-- =====================================================
|
8
|
+
|
9
|
+
-- Users table - Central user registry
|
10
|
+
CREATE TABLE IF NOT EXISTS users (
|
11
|
+
user_id VARCHAR(255) PRIMARY KEY,
|
12
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
13
|
+
username VARCHAR(255),
|
14
|
+
password_hash VARCHAR(255), -- Optional, for local auth
|
15
|
+
first_name VARCHAR(255),
|
16
|
+
last_name VARCHAR(255),
|
17
|
+
display_name VARCHAR(255),
|
18
|
+
|
19
|
+
-- Admin node compatibility fields
|
20
|
+
roles JSONB DEFAULT '[]', -- For compatibility with PermissionCheckNode
|
21
|
+
attributes JSONB DEFAULT '{}', -- User attributes for ABAC
|
22
|
+
|
23
|
+
-- Status and lifecycle
|
24
|
+
status VARCHAR(50) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'suspended', 'pending', 'deleted')),
|
25
|
+
is_active BOOLEAN DEFAULT TRUE,
|
26
|
+
is_system_user BOOLEAN DEFAULT FALSE,
|
27
|
+
|
28
|
+
-- Multi-tenancy
|
29
|
+
tenant_id VARCHAR(255) NOT NULL,
|
30
|
+
|
31
|
+
-- External auth integration
|
32
|
+
external_auth_id VARCHAR(255), -- For SSO integration
|
33
|
+
auth_provider VARCHAR(100), -- 'local', 'oauth2', 'saml', etc.
|
34
|
+
|
35
|
+
-- Audit fields
|
36
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
37
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
38
|
+
created_by VARCHAR(255) DEFAULT 'system',
|
39
|
+
last_login_at TIMESTAMP WITH TIME ZONE,
|
40
|
+
|
41
|
+
-- Constraints
|
42
|
+
UNIQUE(email, tenant_id),
|
43
|
+
UNIQUE(username, tenant_id) -- Allow same username across tenants
|
44
|
+
);
|
45
|
+
|
46
|
+
-- =====================================================
|
47
|
+
-- Role-Based Access Control (RBAC)
|
48
|
+
-- =====================================================
|
49
|
+
|
50
|
+
-- Roles table - Hierarchical role definitions
|
51
|
+
CREATE TABLE IF NOT EXISTS roles (
|
52
|
+
role_id VARCHAR(255) PRIMARY KEY,
|
53
|
+
name VARCHAR(255) NOT NULL,
|
54
|
+
description TEXT,
|
55
|
+
role_type VARCHAR(50) DEFAULT 'custom' CHECK (role_type IN ('system', 'custom', 'template', 'temporary')),
|
56
|
+
|
57
|
+
-- RBAC permissions and hierarchy
|
58
|
+
permissions JSONB DEFAULT '[]', -- Direct permissions
|
59
|
+
parent_roles JSONB DEFAULT '[]', -- Role inheritance
|
60
|
+
child_roles JSONB DEFAULT '[]', -- Derived roles (maintained automatically)
|
61
|
+
|
62
|
+
-- ABAC attributes and conditions
|
63
|
+
attributes JSONB DEFAULT '{}', -- Role attributes
|
64
|
+
conditions JSONB DEFAULT '{}', -- Dynamic conditions for role activation
|
65
|
+
|
66
|
+
-- Lifecycle and constraints
|
67
|
+
is_active BOOLEAN DEFAULT TRUE,
|
68
|
+
is_system_role BOOLEAN DEFAULT FALSE,
|
69
|
+
expires_at TIMESTAMP WITH TIME ZONE, -- For temporary roles
|
70
|
+
|
71
|
+
-- Multi-tenancy
|
72
|
+
tenant_id VARCHAR(255) NOT NULL,
|
73
|
+
|
74
|
+
-- Audit fields
|
75
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
76
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
77
|
+
created_by VARCHAR(255) DEFAULT 'system',
|
78
|
+
|
79
|
+
-- Constraints
|
80
|
+
UNIQUE(name, tenant_id)
|
81
|
+
);
|
82
|
+
|
83
|
+
-- User Role Assignments - Many-to-many with metadata
|
84
|
+
CREATE TABLE IF NOT EXISTS user_role_assignments (
|
85
|
+
id SERIAL PRIMARY KEY,
|
86
|
+
user_id VARCHAR(255) NOT NULL,
|
87
|
+
role_id VARCHAR(255) NOT NULL,
|
88
|
+
tenant_id VARCHAR(255) NOT NULL,
|
89
|
+
|
90
|
+
-- Assignment metadata
|
91
|
+
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
92
|
+
assigned_by VARCHAR(255) DEFAULT 'system',
|
93
|
+
expires_at TIMESTAMP WITH TIME ZONE, -- For temporary assignments
|
94
|
+
|
95
|
+
-- Conditional assignments (ABAC)
|
96
|
+
conditions JSONB DEFAULT '{}', -- When this assignment is active
|
97
|
+
context_requirements JSONB DEFAULT '{}', -- Required context for activation
|
98
|
+
|
99
|
+
-- Status
|
100
|
+
is_active BOOLEAN DEFAULT TRUE,
|
101
|
+
is_inherited BOOLEAN DEFAULT FALSE, -- True if inherited from group/org
|
102
|
+
|
103
|
+
-- Constraints
|
104
|
+
UNIQUE(user_id, role_id, tenant_id),
|
105
|
+
|
106
|
+
-- Foreign keys (enforced at application level for flexibility)
|
107
|
+
CONSTRAINT fk_user_role_user CHECK (user_id IS NOT NULL),
|
108
|
+
CONSTRAINT fk_user_role_role CHECK (role_id IS NOT NULL)
|
109
|
+
);
|
110
|
+
|
111
|
+
-- =====================================================
|
112
|
+
-- Permission Management and Caching
|
113
|
+
-- =====================================================
|
114
|
+
|
115
|
+
-- Permission definitions - Centralized permission registry
|
116
|
+
CREATE TABLE IF NOT EXISTS permissions (
|
117
|
+
permission_id VARCHAR(255) PRIMARY KEY,
|
118
|
+
name VARCHAR(255) NOT NULL,
|
119
|
+
description TEXT,
|
120
|
+
resource_type VARCHAR(100) NOT NULL, -- 'workflow', 'node', 'data', etc.
|
121
|
+
action VARCHAR(100) NOT NULL, -- 'read', 'write', 'execute', etc.
|
122
|
+
|
123
|
+
-- Permission metadata
|
124
|
+
scope VARCHAR(100) DEFAULT 'tenant', -- 'global', 'tenant', 'user'
|
125
|
+
is_system_permission BOOLEAN DEFAULT FALSE,
|
126
|
+
|
127
|
+
-- ABAC conditions
|
128
|
+
default_conditions JSONB DEFAULT '{}', -- Default conditions for this permission
|
129
|
+
required_attributes JSONB DEFAULT '{}', -- Required user/resource attributes
|
130
|
+
|
131
|
+
-- Multi-tenancy
|
132
|
+
tenant_id VARCHAR(255) NOT NULL,
|
133
|
+
|
134
|
+
-- Audit fields
|
135
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
136
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
137
|
+
|
138
|
+
-- Constraints
|
139
|
+
UNIQUE(name, resource_type, action, tenant_id)
|
140
|
+
);
|
141
|
+
|
142
|
+
-- Permission Cache - High-performance permission checking
|
143
|
+
CREATE TABLE IF NOT EXISTS permission_cache (
|
144
|
+
cache_key VARCHAR(512) PRIMARY KEY,
|
145
|
+
user_id VARCHAR(255) NOT NULL,
|
146
|
+
resource VARCHAR(255) NOT NULL,
|
147
|
+
permission VARCHAR(255) NOT NULL,
|
148
|
+
tenant_id VARCHAR(255) NOT NULL,
|
149
|
+
|
150
|
+
-- Cache result
|
151
|
+
result BOOLEAN NOT NULL,
|
152
|
+
decision_path JSONB, -- How the decision was made (for auditing)
|
153
|
+
context_hash VARCHAR(64), -- Hash of context used for decision
|
154
|
+
|
155
|
+
-- Cache metadata
|
156
|
+
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
157
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
158
|
+
hit_count INTEGER DEFAULT 0,
|
159
|
+
last_accessed TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
160
|
+
|
161
|
+
-- Indexes will be created separately
|
162
|
+
CHECK (expires_at > created_at)
|
163
|
+
);
|
164
|
+
|
165
|
+
-- =====================================================
|
166
|
+
-- Attribute-Based Access Control (ABAC)
|
167
|
+
-- =====================================================
|
168
|
+
|
169
|
+
-- User Attributes - Dynamic user properties for ABAC
|
170
|
+
CREATE TABLE IF NOT EXISTS user_attributes (
|
171
|
+
id SERIAL PRIMARY KEY,
|
172
|
+
user_id VARCHAR(255) NOT NULL,
|
173
|
+
tenant_id VARCHAR(255) NOT NULL,
|
174
|
+
|
175
|
+
-- Attribute definition
|
176
|
+
attribute_name VARCHAR(255) NOT NULL,
|
177
|
+
attribute_value JSONB NOT NULL,
|
178
|
+
attribute_type VARCHAR(50) DEFAULT 'string', -- 'string', 'number', 'boolean', 'array', 'object'
|
179
|
+
|
180
|
+
-- Attribute metadata
|
181
|
+
is_computed BOOLEAN DEFAULT FALSE, -- True if computed from other attributes
|
182
|
+
computation_rule JSONB, -- How to compute this attribute
|
183
|
+
source VARCHAR(100) DEFAULT 'manual', -- 'manual', 'computed', 'imported', 'inherited'
|
184
|
+
|
185
|
+
-- Lifecycle
|
186
|
+
is_active BOOLEAN DEFAULT TRUE,
|
187
|
+
expires_at TIMESTAMP WITH TIME ZONE,
|
188
|
+
|
189
|
+
-- Audit fields
|
190
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
191
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
192
|
+
created_by VARCHAR(255) DEFAULT 'system',
|
193
|
+
|
194
|
+
-- Constraints
|
195
|
+
UNIQUE(user_id, attribute_name, tenant_id)
|
196
|
+
);
|
197
|
+
|
198
|
+
-- Resource Attributes - Dynamic resource properties for ABAC
|
199
|
+
CREATE TABLE IF NOT EXISTS resource_attributes (
|
200
|
+
id SERIAL PRIMARY KEY,
|
201
|
+
resource_id VARCHAR(255) NOT NULL,
|
202
|
+
resource_type VARCHAR(100) NOT NULL, -- 'workflow', 'node', 'data', etc.
|
203
|
+
tenant_id VARCHAR(255) NOT NULL,
|
204
|
+
|
205
|
+
-- Attribute definition
|
206
|
+
attribute_name VARCHAR(255) NOT NULL,
|
207
|
+
attribute_value JSONB NOT NULL,
|
208
|
+
attribute_type VARCHAR(50) DEFAULT 'string',
|
209
|
+
|
210
|
+
-- Attribute metadata
|
211
|
+
is_computed BOOLEAN DEFAULT FALSE,
|
212
|
+
computation_rule JSONB,
|
213
|
+
source VARCHAR(100) DEFAULT 'manual',
|
214
|
+
|
215
|
+
-- Lifecycle
|
216
|
+
is_active BOOLEAN DEFAULT TRUE,
|
217
|
+
expires_at TIMESTAMP WITH TIME ZONE,
|
218
|
+
|
219
|
+
-- Audit fields
|
220
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
221
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
222
|
+
created_by VARCHAR(255) DEFAULT 'system',
|
223
|
+
|
224
|
+
-- Constraints
|
225
|
+
UNIQUE(resource_id, attribute_name, tenant_id)
|
226
|
+
);
|
227
|
+
|
228
|
+
-- =====================================================
|
229
|
+
-- Sessions and Security
|
230
|
+
-- =====================================================
|
231
|
+
|
232
|
+
-- User Sessions - Track active user sessions
|
233
|
+
CREATE TABLE IF NOT EXISTS user_sessions (
|
234
|
+
session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
235
|
+
user_id VARCHAR(255) NOT NULL,
|
236
|
+
tenant_id VARCHAR(255) NOT NULL,
|
237
|
+
|
238
|
+
-- Session data
|
239
|
+
session_token_hash VARCHAR(255) UNIQUE NOT NULL,
|
240
|
+
refresh_token_hash VARCHAR(255),
|
241
|
+
device_info JSONB DEFAULT '{}',
|
242
|
+
ip_address INET,
|
243
|
+
user_agent TEXT,
|
244
|
+
|
245
|
+
-- Session lifecycle
|
246
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
247
|
+
last_accessed TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
248
|
+
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
249
|
+
is_active BOOLEAN DEFAULT TRUE,
|
250
|
+
|
251
|
+
-- Security
|
252
|
+
failed_attempts INTEGER DEFAULT 0,
|
253
|
+
locked_until TIMESTAMP WITH TIME ZONE,
|
254
|
+
|
255
|
+
CHECK (expires_at > created_at)
|
256
|
+
);
|
257
|
+
|
258
|
+
-- =====================================================
|
259
|
+
-- Audit and Compliance
|
260
|
+
-- =====================================================
|
261
|
+
|
262
|
+
-- Admin Audit Log - Comprehensive audit trail
|
263
|
+
CREATE TABLE IF NOT EXISTS admin_audit_log (
|
264
|
+
id SERIAL PRIMARY KEY,
|
265
|
+
|
266
|
+
-- Who, What, When, Where
|
267
|
+
user_id VARCHAR(255),
|
268
|
+
tenant_id VARCHAR(255) NOT NULL,
|
269
|
+
action VARCHAR(100) NOT NULL,
|
270
|
+
resource_type VARCHAR(100) NOT NULL,
|
271
|
+
resource_id VARCHAR(255),
|
272
|
+
|
273
|
+
-- Action details
|
274
|
+
operation VARCHAR(100), -- 'create', 'read', 'update', 'delete', 'execute'
|
275
|
+
old_values JSONB, -- Before state
|
276
|
+
new_values JSONB, -- After state
|
277
|
+
context JSONB DEFAULT '{}', -- Request context
|
278
|
+
|
279
|
+
-- Result
|
280
|
+
success BOOLEAN NOT NULL,
|
281
|
+
error_message TEXT,
|
282
|
+
duration_ms INTEGER,
|
283
|
+
|
284
|
+
-- Request metadata
|
285
|
+
ip_address INET,
|
286
|
+
user_agent TEXT,
|
287
|
+
session_id UUID,
|
288
|
+
request_id VARCHAR(255),
|
289
|
+
|
290
|
+
-- Timestamp
|
291
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
292
|
+
);
|
293
|
+
|
294
|
+
-- =====================================================
|
295
|
+
-- Performance Indexes
|
296
|
+
-- =====================================================
|
297
|
+
|
298
|
+
-- User indexes
|
299
|
+
CREATE INDEX IF NOT EXISTS idx_users_tenant_status ON users(tenant_id, status);
|
300
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
301
|
+
CREATE INDEX IF NOT EXISTS idx_users_external_auth ON users(external_auth_id, auth_provider);
|
302
|
+
CREATE INDEX IF NOT EXISTS idx_users_last_login ON users(last_login_at);
|
303
|
+
|
304
|
+
-- Role indexes
|
305
|
+
CREATE INDEX IF NOT EXISTS idx_roles_tenant_active ON roles(tenant_id, is_active);
|
306
|
+
CREATE INDEX IF NOT EXISTS idx_roles_type ON roles(role_type);
|
307
|
+
CREATE INDEX IF NOT EXISTS idx_roles_parent_roles ON roles USING GIN(parent_roles);
|
308
|
+
CREATE INDEX IF NOT EXISTS idx_roles_permissions ON roles USING GIN(permissions);
|
309
|
+
|
310
|
+
-- Assignment indexes
|
311
|
+
CREATE INDEX IF NOT EXISTS idx_user_roles_user ON user_role_assignments(user_id, tenant_id);
|
312
|
+
CREATE INDEX IF NOT EXISTS idx_user_roles_role ON user_role_assignments(role_id, tenant_id);
|
313
|
+
CREATE INDEX IF NOT EXISTS idx_user_roles_active ON user_role_assignments(is_active, expires_at);
|
314
|
+
|
315
|
+
-- Permission cache indexes
|
316
|
+
CREATE INDEX IF NOT EXISTS idx_permission_cache_user ON permission_cache(user_id, tenant_id);
|
317
|
+
CREATE INDEX IF NOT EXISTS idx_permission_cache_expires ON permission_cache(expires_at);
|
318
|
+
CREATE INDEX IF NOT EXISTS idx_permission_cache_resource ON permission_cache(resource, permission);
|
319
|
+
|
320
|
+
-- Attribute indexes
|
321
|
+
CREATE INDEX IF NOT EXISTS idx_user_attributes_user ON user_attributes(user_id, tenant_id);
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_user_attributes_name ON user_attributes(attribute_name, is_active);
|
323
|
+
CREATE INDEX IF NOT EXISTS idx_resource_attributes_resource ON resource_attributes(resource_id, resource_type);
|
324
|
+
|
325
|
+
-- Session indexes
|
326
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON user_sessions(user_id, tenant_id);
|
327
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(session_token_hash);
|
328
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON user_sessions(expires_at, is_active);
|
329
|
+
|
330
|
+
-- Audit indexes
|
331
|
+
CREATE INDEX IF NOT EXISTS idx_audit_user ON admin_audit_log(user_id, tenant_id);
|
332
|
+
CREATE INDEX IF NOT EXISTS idx_audit_resource ON admin_audit_log(resource_type, resource_id);
|
333
|
+
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON admin_audit_log(created_at);
|
334
|
+
CREATE INDEX IF NOT EXISTS idx_audit_action ON admin_audit_log(action, operation);
|
335
|
+
|
336
|
+
-- =====================================================
|
337
|
+
-- Data Integrity and Maintenance
|
338
|
+
-- =====================================================
|
339
|
+
|
340
|
+
-- Auto-update timestamps trigger function
|
341
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
342
|
+
RETURNS TRIGGER AS $$
|
343
|
+
BEGIN
|
344
|
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
345
|
+
RETURN NEW;
|
346
|
+
END;
|
347
|
+
$$ language 'plpgsql';
|
348
|
+
|
349
|
+
-- Apply auto-update triggers
|
350
|
+
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
|
351
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
352
|
+
|
353
|
+
CREATE TRIGGER update_roles_updated_at BEFORE UPDATE ON roles
|
354
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
355
|
+
|
356
|
+
CREATE TRIGGER update_permissions_updated_at BEFORE UPDATE ON permissions
|
357
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
358
|
+
|
359
|
+
CREATE TRIGGER update_user_attributes_updated_at BEFORE UPDATE ON user_attributes
|
360
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
361
|
+
|
362
|
+
CREATE TRIGGER update_resource_attributes_updated_at BEFORE UPDATE ON resource_attributes
|
363
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
364
|
+
|
365
|
+
-- Cache cleanup function
|
366
|
+
CREATE OR REPLACE FUNCTION cleanup_expired_cache()
|
367
|
+
RETURNS INTEGER AS $$
|
368
|
+
DECLARE
|
369
|
+
deleted_count INTEGER;
|
370
|
+
BEGIN
|
371
|
+
DELETE FROM permission_cache WHERE expires_at < CURRENT_TIMESTAMP;
|
372
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
373
|
+
RETURN deleted_count;
|
374
|
+
END;
|
375
|
+
$$ LANGUAGE plpgsql;
|
376
|
+
|
377
|
+
-- Session cleanup function
|
378
|
+
CREATE OR REPLACE FUNCTION cleanup_expired_sessions()
|
379
|
+
RETURNS INTEGER AS $$
|
380
|
+
DECLARE
|
381
|
+
deleted_count INTEGER;
|
382
|
+
BEGIN
|
383
|
+
DELETE FROM user_sessions WHERE expires_at < CURRENT_TIMESTAMP;
|
384
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
385
|
+
RETURN deleted_count;
|
386
|
+
END;
|
387
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,249 @@
|
|
1
|
+
"""Enhanced tenant isolation utilities for admin nodes.
|
2
|
+
|
3
|
+
This module provides robust tenant isolation mechanisms to ensure that
|
4
|
+
multi-tenant operations properly enforce data boundaries and prevent
|
5
|
+
cross-tenant access.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from dataclasses import dataclass
|
10
|
+
from typing import Any, Dict, List, Optional, Set
|
11
|
+
|
12
|
+
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class TenantContext:
|
19
|
+
"""Represents the context for a specific tenant."""
|
20
|
+
|
21
|
+
tenant_id: str
|
22
|
+
permissions: Set[str]
|
23
|
+
user_ids: Set[str]
|
24
|
+
role_ids: Set[str]
|
25
|
+
resource_prefixes: Set[str]
|
26
|
+
|
27
|
+
|
28
|
+
class TenantIsolationManager:
|
29
|
+
"""Manages tenant isolation for admin operations."""
|
30
|
+
|
31
|
+
def __init__(self, db_node):
|
32
|
+
"""
|
33
|
+
Initialize tenant isolation manager.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
db_node: Database node for tenant context queries
|
37
|
+
"""
|
38
|
+
self.db_node = db_node
|
39
|
+
self._tenant_cache = {}
|
40
|
+
|
41
|
+
def get_tenant_context(self, tenant_id: str) -> TenantContext:
|
42
|
+
"""
|
43
|
+
Get the context for a specific tenant.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
tenant_id: The tenant ID
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
TenantContext with tenant-specific data
|
50
|
+
"""
|
51
|
+
if tenant_id not in self._tenant_cache:
|
52
|
+
self._tenant_cache[tenant_id] = self._load_tenant_context(tenant_id)
|
53
|
+
|
54
|
+
return self._tenant_cache[tenant_id]
|
55
|
+
|
56
|
+
def _load_tenant_context(self, tenant_id: str) -> TenantContext:
|
57
|
+
"""Load tenant context from database."""
|
58
|
+
# Get all users for this tenant
|
59
|
+
users_query = """
|
60
|
+
SELECT user_id FROM users WHERE tenant_id = $1 AND status = 'active'
|
61
|
+
"""
|
62
|
+
users_result = self.db_node.run(
|
63
|
+
query=users_query, parameters=[tenant_id], result_format="dict"
|
64
|
+
)
|
65
|
+
user_ids = {row["user_id"] for row in users_result.get("data", [])}
|
66
|
+
|
67
|
+
# Get all roles for this tenant
|
68
|
+
roles_query = """
|
69
|
+
SELECT role_id FROM roles WHERE tenant_id = $1 AND is_active = true
|
70
|
+
"""
|
71
|
+
roles_result = self.db_node.run(
|
72
|
+
query=roles_query, parameters=[tenant_id], result_format="dict"
|
73
|
+
)
|
74
|
+
role_ids = {row["role_id"] for row in roles_result.get("data", [])}
|
75
|
+
|
76
|
+
# Get all permissions for this tenant (from roles)
|
77
|
+
permissions_query = """
|
78
|
+
SELECT DISTINCT unnest(
|
79
|
+
CASE
|
80
|
+
WHEN jsonb_typeof(permissions) = 'array'
|
81
|
+
THEN ARRAY(SELECT jsonb_array_elements_text(permissions))
|
82
|
+
ELSE ARRAY[]::text[]
|
83
|
+
END
|
84
|
+
) as permission
|
85
|
+
FROM roles
|
86
|
+
WHERE tenant_id = $1 AND is_active = true
|
87
|
+
"""
|
88
|
+
permissions_result = self.db_node.run(
|
89
|
+
query=permissions_query, parameters=[tenant_id], result_format="dict"
|
90
|
+
)
|
91
|
+
permissions = {row["permission"] for row in permissions_result.get("data", [])}
|
92
|
+
|
93
|
+
# Create resource prefixes for this tenant
|
94
|
+
resource_prefixes = {f"{tenant_id}:*", "*"}
|
95
|
+
|
96
|
+
return TenantContext(
|
97
|
+
tenant_id=tenant_id,
|
98
|
+
permissions=permissions,
|
99
|
+
user_ids=user_ids,
|
100
|
+
role_ids=role_ids,
|
101
|
+
resource_prefixes=resource_prefixes,
|
102
|
+
)
|
103
|
+
|
104
|
+
def validate_user_tenant_access(self, user_id: str, target_tenant_id: str) -> bool:
|
105
|
+
"""
|
106
|
+
Validate that a user has access within a specific tenant.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
user_id: The user ID to check
|
110
|
+
target_tenant_id: The tenant being accessed
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
True if access is allowed, False otherwise
|
114
|
+
"""
|
115
|
+
tenant_context = self.get_tenant_context(target_tenant_id)
|
116
|
+
return user_id in tenant_context.user_ids
|
117
|
+
|
118
|
+
def validate_role_tenant_access(self, role_id: str, target_tenant_id: str) -> bool:
|
119
|
+
"""
|
120
|
+
Validate that a role belongs to a specific tenant.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
role_id: The role ID to check
|
124
|
+
target_tenant_id: The tenant being accessed
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
True if role belongs to tenant, False otherwise
|
128
|
+
"""
|
129
|
+
tenant_context = self.get_tenant_context(target_tenant_id)
|
130
|
+
return role_id in tenant_context.role_ids
|
131
|
+
|
132
|
+
def check_cross_tenant_permission(
|
133
|
+
self,
|
134
|
+
user_id: str,
|
135
|
+
user_tenant_id: str,
|
136
|
+
resource_tenant_id: str,
|
137
|
+
permission: str,
|
138
|
+
) -> bool:
|
139
|
+
"""
|
140
|
+
Check if a user from one tenant can access resources in another tenant.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
user_id: The user attempting access
|
144
|
+
user_tenant_id: The tenant the user belongs to
|
145
|
+
resource_tenant_id: The tenant of the resource being accessed
|
146
|
+
permission: The permission being requested
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
True if cross-tenant access is allowed, False otherwise
|
150
|
+
"""
|
151
|
+
# For now, enforce strict tenant isolation
|
152
|
+
# Users can only access resources in their own tenant
|
153
|
+
if user_tenant_id != resource_tenant_id:
|
154
|
+
logger.debug(
|
155
|
+
f"Cross-tenant access denied: user {user_id} from {user_tenant_id} "
|
156
|
+
f"attempting to access {resource_tenant_id}"
|
157
|
+
)
|
158
|
+
return False
|
159
|
+
|
160
|
+
# Same tenant access - check if user exists in tenant
|
161
|
+
return self.validate_user_tenant_access(user_id, resource_tenant_id)
|
162
|
+
|
163
|
+
def enforce_tenant_isolation(
|
164
|
+
self, user_id: str, user_tenant_id: str, operation_tenant_id: str
|
165
|
+
) -> None:
|
166
|
+
"""
|
167
|
+
Enforce tenant isolation for an operation.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
user_id: The user performing the operation
|
171
|
+
user_tenant_id: The tenant the user belongs to
|
172
|
+
operation_tenant_id: The tenant context for the operation
|
173
|
+
|
174
|
+
Raises:
|
175
|
+
NodeValidationError: If tenant isolation is violated
|
176
|
+
"""
|
177
|
+
if not self.check_cross_tenant_permission(
|
178
|
+
user_id, user_tenant_id, operation_tenant_id, "access"
|
179
|
+
):
|
180
|
+
raise NodeValidationError(
|
181
|
+
f"Tenant isolation violation: user {user_id} from tenant "
|
182
|
+
f"{user_tenant_id} cannot access tenant {operation_tenant_id}"
|
183
|
+
)
|
184
|
+
|
185
|
+
def get_tenant_scoped_permission(
|
186
|
+
self, permission: str, tenant_id: str, resource_id: Optional[str] = None
|
187
|
+
) -> str:
|
188
|
+
"""
|
189
|
+
Create a tenant-scoped permission string.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
permission: Base permission (e.g., "read", "write")
|
193
|
+
tenant_id: Tenant ID
|
194
|
+
resource_id: Optional resource ID
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
Tenant-scoped permission string
|
198
|
+
"""
|
199
|
+
if resource_id:
|
200
|
+
return f"{tenant_id}:{resource_id}:{permission}"
|
201
|
+
else:
|
202
|
+
return f"{tenant_id}:*:{permission}"
|
203
|
+
|
204
|
+
def clear_tenant_cache(self, tenant_id: Optional[str] = None) -> None:
|
205
|
+
"""
|
206
|
+
Clear the tenant context cache.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
tenant_id: Specific tenant to clear, or None to clear all
|
210
|
+
"""
|
211
|
+
if tenant_id:
|
212
|
+
self._tenant_cache.pop(tenant_id, None)
|
213
|
+
else:
|
214
|
+
self._tenant_cache.clear()
|
215
|
+
|
216
|
+
logger.debug(f"Cleared tenant cache for {tenant_id or 'all tenants'}")
|
217
|
+
|
218
|
+
|
219
|
+
def enforce_tenant_boundary(tenant_id_param: str = "tenant_id"):
|
220
|
+
"""
|
221
|
+
Decorator to enforce tenant boundaries on admin node methods.
|
222
|
+
|
223
|
+
Args:
|
224
|
+
tenant_id_param: Name of the parameter containing the tenant ID
|
225
|
+
"""
|
226
|
+
|
227
|
+
def decorator(func):
|
228
|
+
def wrapper(self, *args, **kwargs):
|
229
|
+
# Extract tenant ID from parameters
|
230
|
+
tenant_id = kwargs.get(tenant_id_param)
|
231
|
+
if not tenant_id:
|
232
|
+
raise NodeValidationError(
|
233
|
+
f"Missing required parameter: {tenant_id_param}"
|
234
|
+
)
|
235
|
+
|
236
|
+
# Create tenant isolation manager if not exists
|
237
|
+
if not hasattr(self, "_tenant_isolation"):
|
238
|
+
self._tenant_isolation = TenantIsolationManager(self._db_node)
|
239
|
+
|
240
|
+
# Perform the operation within tenant context
|
241
|
+
try:
|
242
|
+
return func(self, *args, **kwargs)
|
243
|
+
except Exception as e:
|
244
|
+
logger.error(f"Tenant-scoped operation failed for {tenant_id}: {e}")
|
245
|
+
raise
|
246
|
+
|
247
|
+
return wrapper
|
248
|
+
|
249
|
+
return decorator
|