genxai-framework 0.1.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 (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,226 @@
1
+ """OAuth 2.0 integration for GenXAI."""
2
+
3
+ import requests
4
+ from typing import Dict, Any, Optional
5
+ from abc import ABC, abstractmethod
6
+ from urllib.parse import urlencode
7
+
8
+
9
+ class OAuthProvider(ABC):
10
+ """Base OAuth 2.0 provider."""
11
+
12
+ def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
13
+ """Initialize OAuth provider.
14
+
15
+ Args:
16
+ client_id: OAuth client ID
17
+ client_secret: OAuth client secret
18
+ redirect_uri: Redirect URI after authentication
19
+ """
20
+ self.client_id = client_id
21
+ self.client_secret = client_secret
22
+ self.redirect_uri = redirect_uri
23
+
24
+ @abstractmethod
25
+ def get_authorization_url(self, state: Optional[str] = None) -> str:
26
+ """Get OAuth authorization URL.
27
+
28
+ Args:
29
+ state: Optional state parameter
30
+
31
+ Returns:
32
+ Authorization URL
33
+ """
34
+ pass
35
+
36
+ @abstractmethod
37
+ def exchange_code(self, code: str) -> Dict[str, Any]:
38
+ """Exchange authorization code for tokens.
39
+
40
+ Args:
41
+ code: Authorization code
42
+
43
+ Returns:
44
+ Token response with access_token, refresh_token, etc.
45
+ """
46
+ pass
47
+
48
+ @abstractmethod
49
+ def get_user_info(self, access_token: str) -> Dict[str, Any]:
50
+ """Get user information.
51
+
52
+ Args:
53
+ access_token: Access token
54
+
55
+ Returns:
56
+ User information
57
+ """
58
+ pass
59
+
60
+
61
+ class GoogleOAuthProvider(OAuthProvider):
62
+ """Google OAuth 2.0 provider."""
63
+
64
+ AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
65
+ TOKEN_URL = "https://oauth2.googleapis.com/token"
66
+ USER_INFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"
67
+
68
+ def get_authorization_url(self, state: Optional[str] = None) -> str:
69
+ """Get Google OAuth authorization URL."""
70
+ params = {
71
+ "client_id": self.client_id,
72
+ "redirect_uri": self.redirect_uri,
73
+ "response_type": "code",
74
+ "scope": "openid email profile",
75
+ "access_type": "offline",
76
+ }
77
+
78
+ if state:
79
+ params["state"] = state
80
+
81
+ return f"{self.AUTH_URL}?{urlencode(params)}"
82
+
83
+ def exchange_code(self, code: str) -> Dict[str, Any]:
84
+ """Exchange authorization code for tokens."""
85
+ data = {
86
+ "client_id": self.client_id,
87
+ "client_secret": self.client_secret,
88
+ "code": code,
89
+ "redirect_uri": self.redirect_uri,
90
+ "grant_type": "authorization_code",
91
+ }
92
+
93
+ response = requests.post(self.TOKEN_URL, data=data)
94
+ response.raise_for_status()
95
+
96
+ return response.json()
97
+
98
+ def get_user_info(self, access_token: str) -> Dict[str, Any]:
99
+ """Get user information from Google."""
100
+ headers = {"Authorization": f"Bearer {access_token}"}
101
+ response = requests.get(self.USER_INFO_URL, headers=headers)
102
+ response.raise_for_status()
103
+
104
+ return response.json()
105
+
106
+
107
+ class GitHubOAuthProvider(OAuthProvider):
108
+ """GitHub OAuth 2.0 provider."""
109
+
110
+ AUTH_URL = "https://github.com/login/oauth/authorize"
111
+ TOKEN_URL = "https://github.com/login/oauth/access_token"
112
+ USER_INFO_URL = "https://api.github.com/user"
113
+
114
+ def get_authorization_url(self, state: Optional[str] = None) -> str:
115
+ """Get GitHub OAuth authorization URL."""
116
+ params = {
117
+ "client_id": self.client_id,
118
+ "redirect_uri": self.redirect_uri,
119
+ "scope": "read:user user:email",
120
+ }
121
+
122
+ if state:
123
+ params["state"] = state
124
+
125
+ return f"{self.AUTH_URL}?{urlencode(params)}"
126
+
127
+ def exchange_code(self, code: str) -> Dict[str, Any]:
128
+ """Exchange authorization code for tokens."""
129
+ data = {
130
+ "client_id": self.client_id,
131
+ "client_secret": self.client_secret,
132
+ "code": code,
133
+ "redirect_uri": self.redirect_uri,
134
+ }
135
+
136
+ headers = {"Accept": "application/json"}
137
+ response = requests.post(self.TOKEN_URL, data=data, headers=headers)
138
+ response.raise_for_status()
139
+
140
+ return response.json()
141
+
142
+ def get_user_info(self, access_token: str) -> Dict[str, Any]:
143
+ """Get user information from GitHub."""
144
+ headers = {
145
+ "Authorization": f"Bearer {access_token}",
146
+ "Accept": "application/json"
147
+ }
148
+ response = requests.get(self.USER_INFO_URL, headers=headers)
149
+ response.raise_for_status()
150
+
151
+ return response.json()
152
+
153
+
154
+ class MicrosoftOAuthProvider(OAuthProvider):
155
+ """Microsoft OAuth 2.0 provider."""
156
+
157
+ AUTH_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
158
+ TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
159
+ USER_INFO_URL = "https://graph.microsoft.com/v1.0/me"
160
+
161
+ def get_authorization_url(self, state: Optional[str] = None) -> str:
162
+ """Get Microsoft OAuth authorization URL."""
163
+ params = {
164
+ "client_id": self.client_id,
165
+ "redirect_uri": self.redirect_uri,
166
+ "response_type": "code",
167
+ "scope": "openid email profile User.Read",
168
+ }
169
+
170
+ if state:
171
+ params["state"] = state
172
+
173
+ return f"{self.AUTH_URL}?{urlencode(params)}"
174
+
175
+ def exchange_code(self, code: str) -> Dict[str, Any]:
176
+ """Exchange authorization code for tokens."""
177
+ data = {
178
+ "client_id": self.client_id,
179
+ "client_secret": self.client_secret,
180
+ "code": code,
181
+ "redirect_uri": self.redirect_uri,
182
+ "grant_type": "authorization_code",
183
+ }
184
+
185
+ response = requests.post(self.TOKEN_URL, data=data)
186
+ response.raise_for_status()
187
+
188
+ return response.json()
189
+
190
+ def get_user_info(self, access_token: str) -> Dict[str, Any]:
191
+ """Get user information from Microsoft."""
192
+ headers = {"Authorization": f"Bearer {access_token}"}
193
+ response = requests.get(self.USER_INFO_URL, headers=headers)
194
+ response.raise_for_status()
195
+
196
+ return response.json()
197
+
198
+
199
+ def create_oauth_provider(
200
+ provider: str,
201
+ client_id: str,
202
+ client_secret: str,
203
+ redirect_uri: str
204
+ ) -> OAuthProvider:
205
+ """Create OAuth provider instance.
206
+
207
+ Args:
208
+ provider: Provider name (google, github, microsoft)
209
+ client_id: OAuth client ID
210
+ client_secret: OAuth client secret
211
+ redirect_uri: Redirect URI
212
+
213
+ Returns:
214
+ OAuthProvider instance
215
+ """
216
+ providers = {
217
+ "google": GoogleOAuthProvider,
218
+ "github": GitHubOAuthProvider,
219
+ "microsoft": MicrosoftOAuthProvider,
220
+ }
221
+
222
+ provider_class = providers.get(provider.lower())
223
+ if not provider_class:
224
+ raise ValueError(f"Unknown OAuth provider: {provider}")
225
+
226
+ return provider_class(client_id, client_secret, redirect_uri)
genxai/security/pii.py ADDED
@@ -0,0 +1,366 @@
1
+ """PII detection and redaction for GenXAI."""
2
+
3
+ import re
4
+ from typing import List, Dict, Any, Optional
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+
8
+
9
+ # PII patterns
10
+ PII_PATTERNS = {
11
+ "email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
12
+ "phone": r'\b(?:\+?1[-.]?)?\(?([0-9]{3})\)?[-.]?([0-9]{3})[-.]?([0-9]{4})\b',
13
+ "ssn": r'\b\d{3}-\d{2}-\d{4}\b',
14
+ "credit_card": r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',
15
+ "ip_address": r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b',
16
+ "api_key": r'\b[A-Za-z0-9]{32,}\b',
17
+ "passport": r'\b[A-Z]{1,2}\d{6,9}\b',
18
+ "driver_license": r'\b[A-Z]{1,2}\d{5,8}\b',
19
+ }
20
+
21
+
22
+ @dataclass
23
+ class PIIMatch:
24
+ """PII match result."""
25
+ pii_type: str
26
+ value: str
27
+ start: int
28
+ end: int
29
+
30
+
31
+ class PIIDetector:
32
+ """Detect PII in text."""
33
+
34
+ def __init__(self, patterns: Optional[Dict[str, str]] = None):
35
+ """Initialize PII detector.
36
+
37
+ Args:
38
+ patterns: Custom PII patterns (default: use built-in patterns)
39
+ """
40
+ self.patterns = patterns or PII_PATTERNS
41
+
42
+ def detect(self, text: str) -> List[PIIMatch]:
43
+ """Detect all PII in text.
44
+
45
+ Args:
46
+ text: Text to scan
47
+
48
+ Returns:
49
+ List of PII matches
50
+ """
51
+ matches = []
52
+
53
+ for pii_type, pattern in self.patterns.items():
54
+ for match in re.finditer(pattern, text):
55
+ matches.append(PIIMatch(
56
+ pii_type=pii_type,
57
+ value=match.group(),
58
+ start=match.start(),
59
+ end=match.end()
60
+ ))
61
+
62
+ # Sort by position
63
+ matches.sort(key=lambda x: x.start)
64
+
65
+ return matches
66
+
67
+ def detect_type(self, text: str, pii_type: str) -> List[PIIMatch]:
68
+ """Detect specific PII type.
69
+
70
+ Args:
71
+ text: Text to scan
72
+ pii_type: PII type to detect
73
+
74
+ Returns:
75
+ List of PII matches
76
+ """
77
+ if pii_type not in self.patterns:
78
+ return []
79
+
80
+ pattern = self.patterns[pii_type]
81
+ matches = []
82
+
83
+ for match in re.finditer(pattern, text):
84
+ matches.append(PIIMatch(
85
+ pii_type=pii_type,
86
+ value=match.group(),
87
+ start=match.start(),
88
+ end=match.end()
89
+ ))
90
+
91
+ return matches
92
+
93
+ def has_pii(self, text: str) -> bool:
94
+ """Check if text contains PII.
95
+
96
+ Args:
97
+ text: Text to check
98
+
99
+ Returns:
100
+ True if PII detected
101
+ """
102
+ for pattern in self.patterns.values():
103
+ if re.search(pattern, text):
104
+ return True
105
+
106
+ return False
107
+
108
+
109
+ class PIIRedactor:
110
+ """Redact PII from text."""
111
+
112
+ def __init__(self, detector: Optional[PIIDetector] = None):
113
+ """Initialize PII redactor.
114
+
115
+ Args:
116
+ detector: PII detector (default: create new detector)
117
+ """
118
+ self.detector = detector or PIIDetector()
119
+
120
+ def redact(
121
+ self,
122
+ text: str,
123
+ replacement: str = "***REDACTED***",
124
+ pii_types: Optional[List[str]] = None
125
+ ) -> str:
126
+ """Redact all PII.
127
+
128
+ Args:
129
+ text: Text to redact
130
+ replacement: Replacement string
131
+ pii_types: Specific PII types to redact (default: all)
132
+
133
+ Returns:
134
+ Redacted text
135
+ """
136
+ matches = self.detector.detect(text)
137
+
138
+ # Filter by PII types if specified
139
+ if pii_types:
140
+ matches = [m for m in matches if m.pii_type in pii_types]
141
+
142
+ # Redact from end to start to preserve positions
143
+ for match in reversed(matches):
144
+ text = text[:match.start] + replacement + text[match.end:]
145
+
146
+ return text
147
+
148
+ def mask(self, text: str, pii_types: Optional[List[str]] = None) -> str:
149
+ """Mask PII (show last 4 characters).
150
+
151
+ Args:
152
+ text: Text to mask
153
+ pii_types: Specific PII types to mask (default: all)
154
+
155
+ Returns:
156
+ Masked text
157
+ """
158
+ matches = self.detector.detect(text)
159
+
160
+ # Filter by PII types if specified
161
+ if pii_types:
162
+ matches = [m for m in matches if m.pii_type in pii_types]
163
+
164
+ # Mask from end to start to preserve positions
165
+ for match in reversed(matches):
166
+ value = match.value
167
+
168
+ if match.pii_type == "email":
169
+ # email@example.com -> e***@example.com
170
+ parts = value.split('@')
171
+ if len(parts) == 2:
172
+ masked = parts[0][0] + '***@' + parts[1]
173
+ else:
174
+ masked = '***'
175
+
176
+ elif match.pii_type in ["phone", "ssn", "credit_card"]:
177
+ # Show last 4 digits
178
+ masked = '***-***-' + value[-4:]
179
+
180
+ elif match.pii_type == "ip_address":
181
+ # Show first octet
182
+ parts = value.split('.')
183
+ masked = parts[0] + '.***.***.***'
184
+
185
+ else:
186
+ # Default: show last 4 characters
187
+ if len(value) > 4:
188
+ masked = '***' + value[-4:]
189
+ else:
190
+ masked = '***'
191
+
192
+ text = text[:match.start] + masked + text[match.end:]
193
+
194
+ return text
195
+
196
+ def redact_dict(
197
+ self,
198
+ data: Dict[str, Any],
199
+ replacement: str = "***REDACTED***"
200
+ ) -> Dict[str, Any]:
201
+ """Redact PII from dictionary recursively.
202
+
203
+ Args:
204
+ data: Dictionary to redact
205
+ replacement: Replacement string
206
+
207
+ Returns:
208
+ Redacted dictionary
209
+ """
210
+ result = {}
211
+
212
+ for key, value in data.items():
213
+ if isinstance(value, str):
214
+ result[key] = self.redact(value, replacement)
215
+ elif isinstance(value, dict):
216
+ result[key] = self.redact_dict(value, replacement)
217
+ elif isinstance(value, list):
218
+ result[key] = [
219
+ self.redact(item, replacement) if isinstance(item, str)
220
+ else self.redact_dict(item, replacement) if isinstance(item, dict)
221
+ else item
222
+ for item in value
223
+ ]
224
+ else:
225
+ result[key] = value
226
+
227
+ return result
228
+
229
+
230
+ class PIIAuditLogger:
231
+ """Log PII access for compliance."""
232
+
233
+ def __init__(self, log_file: str = "pii_audit.log"):
234
+ """Initialize PII audit logger.
235
+
236
+ Args:
237
+ log_file: Path to audit log file
238
+ """
239
+ self.log_file = log_file
240
+
241
+ def log_access(
242
+ self,
243
+ user_id: str,
244
+ pii_type: str,
245
+ action: str,
246
+ context: Dict[str, Any]
247
+ ):
248
+ """Log PII access.
249
+
250
+ Args:
251
+ user_id: User ID
252
+ pii_type: PII type accessed
253
+ action: Action performed (read, write, delete)
254
+ context: Additional context
255
+ """
256
+ log_entry = {
257
+ "timestamp": datetime.utcnow().isoformat(),
258
+ "user_id": user_id,
259
+ "pii_type": pii_type,
260
+ "action": action,
261
+ "context": context
262
+ }
263
+
264
+ with open(self.log_file, 'a') as f:
265
+ import json
266
+ f.write(json.dumps(log_entry) + '\n')
267
+
268
+ def get_logs(
269
+ self,
270
+ user_id: Optional[str] = None,
271
+ pii_type: Optional[str] = None,
272
+ start_time: Optional[datetime] = None,
273
+ end_time: Optional[datetime] = None
274
+ ) -> List[Dict[str, Any]]:
275
+ """Get audit logs with filters.
276
+
277
+ Args:
278
+ user_id: Filter by user ID
279
+ pii_type: Filter by PII type
280
+ start_time: Filter by start time
281
+ end_time: Filter by end time
282
+
283
+ Returns:
284
+ List of log entries
285
+ """
286
+ import json
287
+ logs = []
288
+
289
+ try:
290
+ with open(self.log_file, 'r') as f:
291
+ for line in f:
292
+ try:
293
+ entry = json.loads(line.strip())
294
+
295
+ # Apply filters
296
+ if user_id and entry.get("user_id") != user_id:
297
+ continue
298
+
299
+ if pii_type and entry.get("pii_type") != pii_type:
300
+ continue
301
+
302
+ timestamp = datetime.fromisoformat(entry["timestamp"])
303
+
304
+ if start_time and timestamp < start_time:
305
+ continue
306
+
307
+ if end_time and timestamp > end_time:
308
+ continue
309
+
310
+ logs.append(entry)
311
+
312
+ except json.JSONDecodeError:
313
+ continue
314
+
315
+ except FileNotFoundError:
316
+ pass
317
+
318
+ return logs
319
+
320
+
321
+ # Global instances
322
+ _pii_detector = None
323
+ _pii_redactor = None
324
+ _pii_audit_logger = None
325
+
326
+
327
+ def get_pii_detector() -> PIIDetector:
328
+ """Get global PII detector.
329
+
330
+ Returns:
331
+ PIIDetector instance
332
+ """
333
+ global _pii_detector
334
+
335
+ if _pii_detector is None:
336
+ _pii_detector = PIIDetector()
337
+
338
+ return _pii_detector
339
+
340
+
341
+ def get_pii_redactor() -> PIIRedactor:
342
+ """Get global PII redactor.
343
+
344
+ Returns:
345
+ PIIRedactor instance
346
+ """
347
+ global _pii_redactor
348
+
349
+ if _pii_redactor is None:
350
+ _pii_redactor = PIIRedactor()
351
+
352
+ return _pii_redactor
353
+
354
+
355
+ def get_pii_audit_logger() -> PIIAuditLogger:
356
+ """Get global PII audit logger.
357
+
358
+ Returns:
359
+ PIIAuditLogger instance
360
+ """
361
+ global _pii_audit_logger
362
+
363
+ if _pii_audit_logger is None:
364
+ _pii_audit_logger = PIIAuditLogger()
365
+
366
+ return _pii_audit_logger
@@ -0,0 +1,82 @@
1
+ """Resource-level policy engine for GenXAI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Dict, Optional, Set
8
+
9
+ from genxai.security.rbac import Permission, User, PermissionDenied
10
+ from genxai.security.audit import get_approval_service
11
+
12
+
13
+ class ResourceType(str, Enum):
14
+ """Resource types covered by policy engine."""
15
+
16
+ AGENT = "agent"
17
+ TOOL = "tool"
18
+ WORKFLOW = "workflow"
19
+ MEMORY = "memory"
20
+
21
+
22
+ @dataclass
23
+ class AccessRule:
24
+ """ACL rule for a resource."""
25
+
26
+ permissions: Set[Permission]
27
+ allowed_users: Optional[Set[str]] = None
28
+ requires_approval: bool = False
29
+ approval_request_id: Optional[str] = None
30
+
31
+
32
+ class PolicyEngine:
33
+ """Simple ACL-based policy engine."""
34
+
35
+ def __init__(self) -> None:
36
+ self._rules: Dict[str, AccessRule] = {}
37
+
38
+ def add_rule(self, resource_id: str, rule: AccessRule) -> None:
39
+ self._rules[resource_id] = rule
40
+
41
+ def check(self, user: User, resource_id: str, permission: Permission) -> None:
42
+ rule = self._rules.get(resource_id)
43
+ if rule is None:
44
+ if not user.has_permission(permission):
45
+ raise PermissionDenied(
46
+ f"User {user.user_id} missing permission: {permission.value}"
47
+ )
48
+ return
49
+
50
+ if rule.requires_approval:
51
+ if not rule.approval_request_id:
52
+ approval = get_approval_service().submit(
53
+ f"{permission.value}",
54
+ resource_id,
55
+ user.user_id,
56
+ )
57
+ rule.approval_request_id = approval.request_id
58
+ approval = get_approval_service().get(rule.approval_request_id)
59
+ if not approval or approval.status != "approved":
60
+ raise PermissionDenied(
61
+ f"Approval required for resource {resource_id}"
62
+ )
63
+
64
+ if rule.allowed_users and user.user_id not in rule.allowed_users:
65
+ raise PermissionDenied(
66
+ f"User {user.user_id} not allowed for resource {resource_id}"
67
+ )
68
+
69
+ if permission not in rule.permissions:
70
+ raise PermissionDenied(
71
+ f"Permission {permission.value} denied for resource {resource_id}"
72
+ )
73
+
74
+
75
+ _policy_engine: Optional[PolicyEngine] = None
76
+
77
+
78
+ def get_policy_engine() -> PolicyEngine:
79
+ global _policy_engine
80
+ if _policy_engine is None:
81
+ _policy_engine = PolicyEngine()
82
+ return _policy_engine