aiptx 2.0.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.

Potentially problematic release.


This version of aiptx might be problematic. Click here for more details.

Files changed (165) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +24 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/ptt.py +406 -0
  8. aipt_v2/agents/state.py +168 -0
  9. aipt_v2/app.py +960 -0
  10. aipt_v2/browser/__init__.py +31 -0
  11. aipt_v2/browser/automation.py +458 -0
  12. aipt_v2/browser/crawler.py +453 -0
  13. aipt_v2/cli.py +321 -0
  14. aipt_v2/compliance/__init__.py +71 -0
  15. aipt_v2/compliance/compliance_report.py +449 -0
  16. aipt_v2/compliance/framework_mapper.py +424 -0
  17. aipt_v2/compliance/nist_mapping.py +345 -0
  18. aipt_v2/compliance/owasp_mapping.py +330 -0
  19. aipt_v2/compliance/pci_mapping.py +297 -0
  20. aipt_v2/config.py +288 -0
  21. aipt_v2/core/__init__.py +43 -0
  22. aipt_v2/core/agent.py +630 -0
  23. aipt_v2/core/llm.py +395 -0
  24. aipt_v2/core/memory.py +305 -0
  25. aipt_v2/core/ptt.py +329 -0
  26. aipt_v2/database/__init__.py +14 -0
  27. aipt_v2/database/models.py +232 -0
  28. aipt_v2/database/repository.py +384 -0
  29. aipt_v2/docker/__init__.py +23 -0
  30. aipt_v2/docker/builder.py +260 -0
  31. aipt_v2/docker/manager.py +222 -0
  32. aipt_v2/docker/sandbox.py +371 -0
  33. aipt_v2/evasion/__init__.py +58 -0
  34. aipt_v2/evasion/request_obfuscator.py +272 -0
  35. aipt_v2/evasion/tls_fingerprint.py +285 -0
  36. aipt_v2/evasion/ua_rotator.py +301 -0
  37. aipt_v2/evasion/waf_bypass.py +439 -0
  38. aipt_v2/execution/__init__.py +23 -0
  39. aipt_v2/execution/executor.py +302 -0
  40. aipt_v2/execution/parser.py +544 -0
  41. aipt_v2/execution/terminal.py +337 -0
  42. aipt_v2/health.py +437 -0
  43. aipt_v2/intelligence/__init__.py +85 -0
  44. aipt_v2/intelligence/auth.py +520 -0
  45. aipt_v2/intelligence/chaining.py +775 -0
  46. aipt_v2/intelligence/cve_aipt.py +334 -0
  47. aipt_v2/intelligence/cve_info.py +1111 -0
  48. aipt_v2/intelligence/rag.py +239 -0
  49. aipt_v2/intelligence/scope.py +442 -0
  50. aipt_v2/intelligence/searchers/__init__.py +5 -0
  51. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  52. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  53. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  54. aipt_v2/intelligence/tools.json +443 -0
  55. aipt_v2/intelligence/triage.py +670 -0
  56. aipt_v2/interface/__init__.py +5 -0
  57. aipt_v2/interface/cli.py +230 -0
  58. aipt_v2/interface/main.py +501 -0
  59. aipt_v2/interface/tui.py +1276 -0
  60. aipt_v2/interface/utils.py +583 -0
  61. aipt_v2/llm/__init__.py +39 -0
  62. aipt_v2/llm/config.py +26 -0
  63. aipt_v2/llm/llm.py +514 -0
  64. aipt_v2/llm/memory.py +214 -0
  65. aipt_v2/llm/request_queue.py +89 -0
  66. aipt_v2/llm/utils.py +89 -0
  67. aipt_v2/models/__init__.py +15 -0
  68. aipt_v2/models/findings.py +295 -0
  69. aipt_v2/models/phase_result.py +224 -0
  70. aipt_v2/models/scan_config.py +207 -0
  71. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  72. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  73. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  74. aipt_v2/monitoring/prometheus.yml +60 -0
  75. aipt_v2/orchestration/__init__.py +52 -0
  76. aipt_v2/orchestration/pipeline.py +398 -0
  77. aipt_v2/orchestration/progress.py +300 -0
  78. aipt_v2/orchestration/scheduler.py +296 -0
  79. aipt_v2/orchestrator.py +2284 -0
  80. aipt_v2/payloads/__init__.py +27 -0
  81. aipt_v2/payloads/cmdi.py +150 -0
  82. aipt_v2/payloads/sqli.py +263 -0
  83. aipt_v2/payloads/ssrf.py +204 -0
  84. aipt_v2/payloads/templates.py +222 -0
  85. aipt_v2/payloads/traversal.py +166 -0
  86. aipt_v2/payloads/xss.py +204 -0
  87. aipt_v2/prompts/__init__.py +60 -0
  88. aipt_v2/proxy/__init__.py +29 -0
  89. aipt_v2/proxy/history.py +352 -0
  90. aipt_v2/proxy/interceptor.py +452 -0
  91. aipt_v2/recon/__init__.py +44 -0
  92. aipt_v2/recon/dns.py +241 -0
  93. aipt_v2/recon/osint.py +367 -0
  94. aipt_v2/recon/subdomain.py +372 -0
  95. aipt_v2/recon/tech_detect.py +311 -0
  96. aipt_v2/reports/__init__.py +17 -0
  97. aipt_v2/reports/generator.py +313 -0
  98. aipt_v2/reports/html_report.py +378 -0
  99. aipt_v2/runtime/__init__.py +44 -0
  100. aipt_v2/runtime/base.py +30 -0
  101. aipt_v2/runtime/docker.py +401 -0
  102. aipt_v2/runtime/local.py +346 -0
  103. aipt_v2/runtime/tool_server.py +205 -0
  104. aipt_v2/scanners/__init__.py +28 -0
  105. aipt_v2/scanners/base.py +273 -0
  106. aipt_v2/scanners/nikto.py +244 -0
  107. aipt_v2/scanners/nmap.py +402 -0
  108. aipt_v2/scanners/nuclei.py +273 -0
  109. aipt_v2/scanners/web.py +454 -0
  110. aipt_v2/scripts/security_audit.py +366 -0
  111. aipt_v2/telemetry/__init__.py +7 -0
  112. aipt_v2/telemetry/tracer.py +347 -0
  113. aipt_v2/terminal/__init__.py +28 -0
  114. aipt_v2/terminal/executor.py +400 -0
  115. aipt_v2/terminal/sandbox.py +350 -0
  116. aipt_v2/tools/__init__.py +44 -0
  117. aipt_v2/tools/active_directory/__init__.py +78 -0
  118. aipt_v2/tools/active_directory/ad_config.py +238 -0
  119. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  120. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  121. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  122. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  123. aipt_v2/tools/agents_graph/__init__.py +19 -0
  124. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  125. aipt_v2/tools/api_security/__init__.py +76 -0
  126. aipt_v2/tools/api_security/api_discovery.py +608 -0
  127. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  128. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  129. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  130. aipt_v2/tools/browser/__init__.py +5 -0
  131. aipt_v2/tools/browser/browser_actions.py +238 -0
  132. aipt_v2/tools/browser/browser_instance.py +535 -0
  133. aipt_v2/tools/browser/tab_manager.py +344 -0
  134. aipt_v2/tools/cloud/__init__.py +70 -0
  135. aipt_v2/tools/cloud/cloud_config.py +273 -0
  136. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  137. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  138. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  139. aipt_v2/tools/executor.py +307 -0
  140. aipt_v2/tools/parser.py +408 -0
  141. aipt_v2/tools/proxy/__init__.py +5 -0
  142. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  143. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  144. aipt_v2/tools/registry.py +196 -0
  145. aipt_v2/tools/scanners/__init__.py +343 -0
  146. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  147. aipt_v2/tools/scanners/burp_tool.py +631 -0
  148. aipt_v2/tools/scanners/config.py +156 -0
  149. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  150. aipt_v2/tools/scanners/zap_tool.py +612 -0
  151. aipt_v2/tools/terminal/__init__.py +5 -0
  152. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  153. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  154. aipt_v2/tools/terminal/terminal_session.py +449 -0
  155. aipt_v2/tools/tool_processing.py +108 -0
  156. aipt_v2/utils/__init__.py +17 -0
  157. aipt_v2/utils/logging.py +201 -0
  158. aipt_v2/utils/model_manager.py +187 -0
  159. aipt_v2/utils/searchers/__init__.py +269 -0
  160. aiptx-2.0.2.dist-info/METADATA +324 -0
  161. aiptx-2.0.2.dist-info/RECORD +165 -0
  162. aiptx-2.0.2.dist-info/WHEEL +5 -0
  163. aiptx-2.0.2.dist-info/entry_points.txt +7 -0
  164. aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
  165. aiptx-2.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,520 @@
1
+ """
2
+ AIPT Authenticated Scanning Support
3
+
4
+ Provides authentication mechanisms for scanning protected resources.
5
+ Supports multiple authentication methods:
6
+ - Session cookies
7
+ - Bearer tokens (JWT, OAuth)
8
+ - Basic authentication
9
+ - API keys
10
+ - Custom headers
11
+ - Form-based login automation
12
+
13
+ This enables testing authenticated portions of applications
14
+ (with proper authorization from the client).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import base64
20
+ import hashlib
21
+ import hmac
22
+ import json
23
+ import logging
24
+ import re
25
+ import time
26
+ from dataclasses import dataclass, field
27
+ from datetime import datetime, timedelta
28
+ from enum import Enum
29
+ from typing import Any, Callable
30
+ from urllib.parse import urlencode
31
+
32
+ import httpx
33
+
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class AuthMethod(Enum):
39
+ """Authentication methods supported"""
40
+ NONE = "none"
41
+ COOKIE = "cookie"
42
+ BEARER_TOKEN = "bearer_token"
43
+ BASIC_AUTH = "basic_auth"
44
+ API_KEY = "api_key"
45
+ CUSTOM_HEADER = "custom_header"
46
+ FORM_LOGIN = "form_login"
47
+ OAUTH2 = "oauth2"
48
+ AWS_SIGV4 = "aws_sigv4"
49
+
50
+
51
+ @dataclass
52
+ class AuthCredentials:
53
+ """Authentication credentials"""
54
+ method: AuthMethod = AuthMethod.NONE
55
+
56
+ # For COOKIE method
57
+ cookies: dict[str, str] = field(default_factory=dict)
58
+
59
+ # For BEARER_TOKEN method
60
+ token: str = ""
61
+ token_prefix: str = "Bearer"
62
+
63
+ # For BASIC_AUTH method
64
+ username: str = ""
65
+ password: str = ""
66
+
67
+ # For API_KEY method
68
+ api_key: str = ""
69
+ api_key_header: str = "X-API-Key"
70
+ api_key_in_query: bool = False
71
+ api_key_query_param: str = "api_key"
72
+
73
+ # For CUSTOM_HEADER method
74
+ custom_headers: dict[str, str] = field(default_factory=dict)
75
+
76
+ # For FORM_LOGIN method
77
+ login_url: str = ""
78
+ login_data: dict[str, str] = field(default_factory=dict)
79
+ csrf_field: str = "" # If CSRF token needed
80
+ success_indicator: str = "" # Text/pattern indicating successful login
81
+
82
+ # For OAUTH2 method
83
+ oauth_client_id: str = ""
84
+ oauth_client_secret: str = ""
85
+ oauth_token_url: str = ""
86
+ oauth_scope: str = ""
87
+
88
+ # Token management
89
+ token_expires_at: datetime | None = None
90
+ refresh_token: str = ""
91
+ auto_refresh: bool = True
92
+
93
+ def to_dict(self) -> dict[str, Any]:
94
+ return {
95
+ "method": self.method.value,
96
+ "has_cookies": bool(self.cookies),
97
+ "has_token": bool(self.token),
98
+ "has_basic_auth": bool(self.username),
99
+ "has_api_key": bool(self.api_key),
100
+ "has_custom_headers": bool(self.custom_headers),
101
+ "login_url": self.login_url if self.login_url else None,
102
+ }
103
+
104
+
105
+ @dataclass
106
+ class AuthSession:
107
+ """An authenticated session"""
108
+ credentials: AuthCredentials
109
+ session_id: str
110
+ created_at: datetime = field(default_factory=datetime.utcnow)
111
+ last_used: datetime = field(default_factory=datetime.utcnow)
112
+ is_valid: bool = True
113
+ validation_url: str = ""
114
+ validation_indicator: str = ""
115
+
116
+ # Session state
117
+ cookies: dict[str, str] = field(default_factory=dict)
118
+ headers: dict[str, str] = field(default_factory=dict)
119
+
120
+ # Statistics
121
+ requests_made: int = 0
122
+ auth_failures: int = 0
123
+
124
+
125
+ class AuthenticationManager:
126
+ """
127
+ Manages authentication for scanning sessions.
128
+
129
+ Handles:
130
+ - Multiple authentication methods
131
+ - Token refresh
132
+ - Session validation
133
+ - Header/cookie injection
134
+
135
+ Example:
136
+ creds = AuthCredentials(
137
+ method=AuthMethod.BEARER_TOKEN,
138
+ token="eyJhbGciOi...",
139
+ )
140
+ auth_mgr = AuthenticationManager(creds)
141
+ headers = await auth_mgr.get_auth_headers()
142
+ # Use headers in your requests
143
+ """
144
+
145
+ def __init__(self, credentials: AuthCredentials):
146
+ self.credentials = credentials
147
+ self._session: AuthSession | None = None
148
+ self._http_client: httpx.AsyncClient | None = None
149
+ self._token_lock = asyncio.Lock()
150
+
151
+ async def initialize(self) -> AuthSession:
152
+ """
153
+ Initialize authentication session.
154
+
155
+ For form-based login, this will perform the login.
156
+ For OAuth, this will obtain tokens.
157
+ """
158
+ session_id = hashlib.md5(
159
+ f"{self.credentials.method.value}-{datetime.utcnow().isoformat()}".encode()
160
+ ).hexdigest()[:12]
161
+
162
+ self._session = AuthSession(
163
+ credentials=self.credentials,
164
+ session_id=session_id,
165
+ )
166
+
167
+ # Perform initial authentication based on method
168
+ if self.credentials.method == AuthMethod.FORM_LOGIN:
169
+ await self._perform_form_login()
170
+ elif self.credentials.method == AuthMethod.OAUTH2:
171
+ await self._obtain_oauth_token()
172
+
173
+ # Build initial headers
174
+ self._session.headers = await self._build_auth_headers()
175
+ self._session.cookies = self.credentials.cookies.copy()
176
+
177
+ logger.info(f"Auth session initialized: {session_id} ({self.credentials.method.value})")
178
+ return self._session
179
+
180
+ async def get_auth_headers(self) -> dict[str, str]:
181
+ """
182
+ Get authentication headers for a request.
183
+
184
+ Automatically refreshes tokens if needed.
185
+ """
186
+ if not self._session:
187
+ await self.initialize()
188
+
189
+ # Check if token needs refresh
190
+ if self.credentials.auto_refresh and self._token_expired():
191
+ async with self._token_lock:
192
+ if self._token_expired(): # Double-check after acquiring lock
193
+ await self._refresh_token()
194
+
195
+ self._session.last_used = datetime.utcnow()
196
+ self._session.requests_made += 1
197
+
198
+ return self._session.headers.copy()
199
+
200
+ async def get_auth_cookies(self) -> dict[str, str]:
201
+ """Get authentication cookies"""
202
+ if not self._session:
203
+ await self.initialize()
204
+
205
+ return self._session.cookies.copy()
206
+
207
+ async def _build_auth_headers(self) -> dict[str, str]:
208
+ """Build authentication headers based on method"""
209
+ headers = {}
210
+
211
+ if self.credentials.method == AuthMethod.BEARER_TOKEN:
212
+ headers["Authorization"] = f"{self.credentials.token_prefix} {self.credentials.token}"
213
+
214
+ elif self.credentials.method == AuthMethod.BASIC_AUTH:
215
+ credentials = f"{self.credentials.username}:{self.credentials.password}"
216
+ encoded = base64.b64encode(credentials.encode()).decode()
217
+ headers["Authorization"] = f"Basic {encoded}"
218
+
219
+ elif self.credentials.method == AuthMethod.API_KEY:
220
+ if not self.credentials.api_key_in_query:
221
+ headers[self.credentials.api_key_header] = self.credentials.api_key
222
+
223
+ elif self.credentials.method == AuthMethod.CUSTOM_HEADER:
224
+ headers.update(self.credentials.custom_headers)
225
+
226
+ elif self.credentials.method == AuthMethod.OAUTH2:
227
+ if self.credentials.token:
228
+ headers["Authorization"] = f"Bearer {self.credentials.token}"
229
+
230
+ return headers
231
+
232
+ async def _perform_form_login(self) -> None:
233
+ """Perform form-based login"""
234
+ if not self.credentials.login_url:
235
+ raise ValueError("login_url required for FORM_LOGIN")
236
+
237
+ client = await self._get_http_client()
238
+
239
+ # Get login page (for CSRF token if needed)
240
+ login_data = self.credentials.login_data.copy()
241
+
242
+ if self.credentials.csrf_field:
243
+ # Fetch login page to get CSRF token
244
+ response = await client.get(self.credentials.login_url)
245
+ csrf_token = self._extract_csrf_token(response.text, self.credentials.csrf_field)
246
+ if csrf_token:
247
+ login_data[self.credentials.csrf_field] = csrf_token
248
+
249
+ # Perform login
250
+ response = await client.post(
251
+ self.credentials.login_url,
252
+ data=login_data,
253
+ follow_redirects=True,
254
+ )
255
+
256
+ # Check for success
257
+ if self.credentials.success_indicator:
258
+ if self.credentials.success_indicator not in response.text:
259
+ raise AuthenticationError(
260
+ f"Login failed - success indicator not found: {self.credentials.success_indicator}"
261
+ )
262
+
263
+ # Extract session cookies
264
+ for cookie in client.cookies.jar:
265
+ self._session.cookies[cookie.name] = cookie.value
266
+
267
+ logger.info("Form login successful")
268
+
269
+ def _extract_csrf_token(self, html: str, field_name: str) -> str | None:
270
+ """Extract CSRF token from HTML"""
271
+ # Try common patterns
272
+ patterns = [
273
+ rf'name="{field_name}"[^>]*value="([^"]+)"',
274
+ rf'name=\'{field_name}\'[^>]*value=\'([^\']+)\'',
275
+ rf'value="([^"]+)"[^>]*name="{field_name}"',
276
+ rf'data-csrf="([^"]+)"',
277
+ rf'csrf[_-]?token["\']?\s*[:=]\s*["\']([^"\']+)',
278
+ ]
279
+
280
+ for pattern in patterns:
281
+ match = re.search(pattern, html, re.IGNORECASE)
282
+ if match:
283
+ return match.group(1)
284
+
285
+ return None
286
+
287
+ async def _obtain_oauth_token(self) -> None:
288
+ """Obtain OAuth2 token"""
289
+ if not self.credentials.oauth_token_url:
290
+ raise ValueError("oauth_token_url required for OAUTH2")
291
+
292
+ client = await self._get_http_client()
293
+
294
+ token_data = {
295
+ "grant_type": "client_credentials",
296
+ "client_id": self.credentials.oauth_client_id,
297
+ "client_secret": self.credentials.oauth_client_secret,
298
+ }
299
+
300
+ if self.credentials.oauth_scope:
301
+ token_data["scope"] = self.credentials.oauth_scope
302
+
303
+ response = await client.post(
304
+ self.credentials.oauth_token_url,
305
+ data=token_data,
306
+ )
307
+
308
+ if response.status_code != 200:
309
+ raise AuthenticationError(f"OAuth token request failed: {response.status_code}")
310
+
311
+ data = response.json()
312
+ self.credentials.token = data.get("access_token", "")
313
+ self.credentials.refresh_token = data.get("refresh_token", "")
314
+
315
+ expires_in = data.get("expires_in", 3600)
316
+ self.credentials.token_expires_at = datetime.utcnow() + timedelta(seconds=expires_in - 60)
317
+
318
+ logger.info(f"OAuth token obtained, expires in {expires_in}s")
319
+
320
+ def _token_expired(self) -> bool:
321
+ """Check if current token is expired"""
322
+ if not self.credentials.token_expires_at:
323
+ return False
324
+ return datetime.utcnow() >= self.credentials.token_expires_at
325
+
326
+ async def _refresh_token(self) -> None:
327
+ """Refresh OAuth token"""
328
+ if self.credentials.method == AuthMethod.OAUTH2 and self.credentials.refresh_token:
329
+ client = await self._get_http_client()
330
+
331
+ token_data = {
332
+ "grant_type": "refresh_token",
333
+ "refresh_token": self.credentials.refresh_token,
334
+ "client_id": self.credentials.oauth_client_id,
335
+ "client_secret": self.credentials.oauth_client_secret,
336
+ }
337
+
338
+ response = await client.post(
339
+ self.credentials.oauth_token_url,
340
+ data=token_data,
341
+ )
342
+
343
+ if response.status_code == 200:
344
+ data = response.json()
345
+ self.credentials.token = data.get("access_token", "")
346
+
347
+ new_refresh = data.get("refresh_token")
348
+ if new_refresh:
349
+ self.credentials.refresh_token = new_refresh
350
+
351
+ expires_in = data.get("expires_in", 3600)
352
+ self.credentials.token_expires_at = datetime.utcnow() + timedelta(seconds=expires_in - 60)
353
+
354
+ # Update session headers
355
+ self._session.headers = await self._build_auth_headers()
356
+ logger.info("OAuth token refreshed")
357
+ else:
358
+ logger.warning(f"Token refresh failed: {response.status_code}")
359
+ self._session.auth_failures += 1
360
+ else:
361
+ # For other methods, re-authenticate
362
+ await self.initialize()
363
+
364
+ async def validate_session(self, validation_url: str = "", expected_status: int = 200) -> bool:
365
+ """
366
+ Validate that the authentication session is still valid.
367
+
368
+ Args:
369
+ validation_url: URL to check (should require auth)
370
+ expected_status: Expected HTTP status for valid session
371
+
372
+ Returns:
373
+ True if session is valid
374
+ """
375
+ url = validation_url or self._session.validation_url
376
+ if not url:
377
+ return True # Can't validate without URL
378
+
379
+ try:
380
+ client = await self._get_http_client()
381
+ headers = await self.get_auth_headers()
382
+
383
+ response = await client.get(url, headers=headers)
384
+
385
+ is_valid = response.status_code == expected_status
386
+ self._session.is_valid = is_valid
387
+
388
+ if not is_valid:
389
+ logger.warning(f"Session validation failed: {response.status_code}")
390
+ self._session.auth_failures += 1
391
+
392
+ return is_valid
393
+
394
+ except Exception as e:
395
+ logger.error(f"Session validation error: {e}")
396
+ return False
397
+
398
+ async def _get_http_client(self) -> httpx.AsyncClient:
399
+ """Get HTTP client for auth requests"""
400
+ if self._http_client is None:
401
+ self._http_client = httpx.AsyncClient(
402
+ timeout=30.0,
403
+ follow_redirects=True,
404
+ )
405
+ return self._http_client
406
+
407
+ async def close(self) -> None:
408
+ """Close HTTP client"""
409
+ if self._http_client:
410
+ await self._http_client.aclose()
411
+ self._http_client = None
412
+
413
+ def get_session_stats(self) -> dict[str, Any]:
414
+ """Get session statistics"""
415
+ if not self._session:
416
+ return {"status": "not_initialized"}
417
+
418
+ return {
419
+ "session_id": self._session.session_id,
420
+ "method": self.credentials.method.value,
421
+ "created_at": self._session.created_at.isoformat(),
422
+ "last_used": self._session.last_used.isoformat(),
423
+ "is_valid": self._session.is_valid,
424
+ "requests_made": self._session.requests_made,
425
+ "auth_failures": self._session.auth_failures,
426
+ "token_expires_at": (
427
+ self.credentials.token_expires_at.isoformat()
428
+ if self.credentials.token_expires_at
429
+ else None
430
+ ),
431
+ }
432
+
433
+
434
+ class AuthenticationError(Exception):
435
+ """Authentication failed"""
436
+ pass
437
+
438
+
439
+ # ============================================================================
440
+ # Convenience Functions
441
+ # ============================================================================
442
+
443
+ def create_bearer_auth(token: str, prefix: str = "Bearer") -> AuthCredentials:
444
+ """Create bearer token authentication"""
445
+ return AuthCredentials(
446
+ method=AuthMethod.BEARER_TOKEN,
447
+ token=token,
448
+ token_prefix=prefix,
449
+ )
450
+
451
+
452
+ def create_basic_auth(username: str, password: str) -> AuthCredentials:
453
+ """Create basic authentication"""
454
+ return AuthCredentials(
455
+ method=AuthMethod.BASIC_AUTH,
456
+ username=username,
457
+ password=password,
458
+ )
459
+
460
+
461
+ def create_api_key_auth(
462
+ api_key: str,
463
+ header: str = "X-API-Key",
464
+ in_query: bool = False,
465
+ query_param: str = "api_key",
466
+ ) -> AuthCredentials:
467
+ """Create API key authentication"""
468
+ return AuthCredentials(
469
+ method=AuthMethod.API_KEY,
470
+ api_key=api_key,
471
+ api_key_header=header,
472
+ api_key_in_query=in_query,
473
+ api_key_query_param=query_param,
474
+ )
475
+
476
+
477
+ def create_cookie_auth(cookies: dict[str, str]) -> AuthCredentials:
478
+ """Create cookie-based authentication"""
479
+ return AuthCredentials(
480
+ method=AuthMethod.COOKIE,
481
+ cookies=cookies,
482
+ )
483
+
484
+
485
+ def create_form_login_auth(
486
+ login_url: str,
487
+ username: str,
488
+ password: str,
489
+ username_field: str = "username",
490
+ password_field: str = "password",
491
+ csrf_field: str = "",
492
+ success_indicator: str = "",
493
+ ) -> AuthCredentials:
494
+ """Create form-based login authentication"""
495
+ return AuthCredentials(
496
+ method=AuthMethod.FORM_LOGIN,
497
+ login_url=login_url,
498
+ login_data={
499
+ username_field: username,
500
+ password_field: password,
501
+ },
502
+ csrf_field=csrf_field,
503
+ success_indicator=success_indicator,
504
+ )
505
+
506
+
507
+ def create_oauth2_auth(
508
+ client_id: str,
509
+ client_secret: str,
510
+ token_url: str,
511
+ scope: str = "",
512
+ ) -> AuthCredentials:
513
+ """Create OAuth2 client credentials authentication"""
514
+ return AuthCredentials(
515
+ method=AuthMethod.OAUTH2,
516
+ oauth_client_id=client_id,
517
+ oauth_client_secret=client_secret,
518
+ oauth_token_url=token_url,
519
+ oauth_scope=scope,
520
+ )