souleyez 2.43.26__py3-none-any.whl → 2.43.34__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 (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +23434 -10286
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.26.dist-info/RECORD +0 -379
  356. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ Handles:
7
7
  - Default admin user creation
8
8
  - Tier management for licensing
9
9
  """
10
+
10
11
  import sqlite3
11
12
  import hashlib
12
13
  import secrets
@@ -27,6 +28,7 @@ MIN_PASSWORD_LENGTH = 8
27
28
  @dataclass
28
29
  class User:
29
30
  """User account model."""
31
+
30
32
  id: str
31
33
  username: str
32
34
  email: Optional[str]
@@ -95,10 +97,7 @@ class UserManager:
95
97
  Hex-encoded password hash
96
98
  """
97
99
  key = hashlib.pbkdf2_hmac(
98
- 'sha256',
99
- password.encode('utf-8'),
100
- bytes.fromhex(salt),
101
- HASH_ITERATIONS
100
+ "sha256", password.encode("utf-8"), bytes.fromhex(salt), HASH_ITERATIONS
102
101
  )
103
102
  return key.hex()
104
103
 
@@ -125,13 +124,13 @@ class UserManager:
125
124
  if len(password) < MIN_PASSWORD_LENGTH:
126
125
  return False, f"Password must be at least {MIN_PASSWORD_LENGTH} characters"
127
126
 
128
- if not re.search(r'[A-Z]', password):
127
+ if not re.search(r"[A-Z]", password):
129
128
  return False, "Password must contain at least one uppercase letter"
130
129
 
131
- if not re.search(r'[a-z]', password):
130
+ if not re.search(r"[a-z]", password):
132
131
  return False, "Password must contain at least one lowercase letter"
133
132
 
134
- if not re.search(r'\d', password):
133
+ if not re.search(r"\d", password):
135
134
  return False, "Password must contain at least one digit"
136
135
 
137
136
  if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
@@ -154,7 +153,7 @@ class UserManager:
154
153
  email: Optional[str] = None,
155
154
  role: Role = Role.ANALYST,
156
155
  tier: Tier = Tier.FREE,
157
- skip_password_validation: bool = False
156
+ skip_password_validation: bool = False,
158
157
  ) -> tuple[Optional[User], str]:
159
158
  """
160
159
  Create a new user account.
@@ -174,8 +173,11 @@ class UserManager:
174
173
  if not username or len(username) < 3:
175
174
  return None, "Username must be at least 3 characters"
176
175
 
177
- if not re.match(r'^[a-zA-Z0-9_-]+$', username):
178
- return None, "Username can only contain letters, numbers, underscores, and hyphens"
176
+ if not re.match(r"^[a-zA-Z0-9_-]+$", username):
177
+ return (
178
+ None,
179
+ "Username can only contain letters, numbers, underscores, and hyphens",
180
+ )
179
181
 
180
182
  # Validate password
181
183
  if not skip_password_validation:
@@ -192,15 +194,26 @@ class UserManager:
192
194
 
193
195
  try:
194
196
  conn = self._get_conn()
195
- conn.execute("""
197
+ conn.execute(
198
+ """
196
199
  INSERT INTO users (
197
200
  id, username, password_hash, salt, email, role, tier,
198
201
  is_active, created_at, updated_at
199
202
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
200
- """, (
201
- user_id, username.lower(), password_hash, salt, email,
202
- role.value, tier.value, True, now, now
203
- ))
203
+ """,
204
+ (
205
+ user_id,
206
+ username.lower(),
207
+ password_hash,
208
+ salt,
209
+ email,
210
+ role.value,
211
+ tier.value,
212
+ True,
213
+ now,
214
+ now,
215
+ ),
216
+ )
204
217
  conn.commit()
205
218
  conn.close()
206
219
 
@@ -214,9 +227,7 @@ class UserManager:
214
227
  def get_user_by_id(self, user_id: str) -> Optional[User]:
215
228
  """Get user by ID."""
216
229
  conn = self._get_conn()
217
- row = conn.execute(
218
- "SELECT * FROM users WHERE id = ?", (user_id,)
219
- ).fetchone()
230
+ row = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
220
231
  conn.close()
221
232
 
222
233
  if row is None:
@@ -257,7 +268,7 @@ class UserManager:
257
268
  email: Optional[str] = None,
258
269
  role: Optional[Role] = None,
259
270
  tier: Optional[Tier] = None,
260
- is_active: Optional[bool] = None
271
+ is_active: Optional[bool] = None,
261
272
  ) -> tuple[bool, str]:
262
273
  """
263
274
  Update user fields.
@@ -295,7 +306,7 @@ class UserManager:
295
306
  conn = self._get_conn()
296
307
  result = conn.execute(
297
308
  f"UPDATE users SET {', '.join(updates)} WHERE id = ?", # nosec B608 - column names are whitelisted, not user input
298
- params
309
+ params,
299
310
  )
300
311
  conn.commit()
301
312
  conn.close()
@@ -309,10 +320,7 @@ class UserManager:
309
320
  return False, f"Failed to update user: {e}"
310
321
 
311
322
  def change_password(
312
- self,
313
- user_id: str,
314
- new_password: str,
315
- skip_validation: bool = False
323
+ self, user_id: str, new_password: str, skip_validation: bool = False
316
324
  ) -> tuple[bool, str]:
317
325
  """Change user password."""
318
326
  if not skip_validation:
@@ -325,11 +333,14 @@ class UserManager:
325
333
 
326
334
  try:
327
335
  conn = self._get_conn()
328
- result = conn.execute("""
336
+ result = conn.execute(
337
+ """
329
338
  UPDATE users
330
339
  SET password_hash = ?, salt = ?, updated_at = ?
331
340
  WHERE id = ?
332
- """, (password_hash, salt, datetime.now().isoformat(), user_id))
341
+ """,
342
+ (password_hash, salt, datetime.now().isoformat(), user_id),
343
+ )
333
344
  conn.commit()
334
345
  conn.close()
335
346
 
@@ -397,11 +408,14 @@ class UserManager:
397
408
  return None, "Invalid username or password"
398
409
 
399
410
  # Successful login - reset failed attempts and update last_login
400
- conn.execute("""
411
+ conn.execute(
412
+ """
401
413
  UPDATE users
402
414
  SET failed_login_attempts = 0, locked_until = NULL, last_login = ?
403
415
  WHERE id = ?
404
- """, (datetime.now().isoformat(), user.id))
416
+ """,
417
+ (datetime.now().isoformat(), user.id),
418
+ )
405
419
  conn.commit()
406
420
  conn.close()
407
421
 
@@ -412,11 +426,14 @@ class UserManager:
412
426
  MAX_ATTEMPTS = 5
413
427
  LOCKOUT_MINUTES = 15
414
428
 
415
- conn.execute("""
429
+ conn.execute(
430
+ """
416
431
  UPDATE users
417
432
  SET failed_login_attempts = failed_login_attempts + 1
418
433
  WHERE id = ?
419
- """, (user_id,))
434
+ """,
435
+ (user_id,),
436
+ )
420
437
 
421
438
  # Check if we need to lock the account
422
439
  row = conn.execute(
@@ -425,10 +442,14 @@ class UserManager:
425
442
 
426
443
  if row and row[0] >= MAX_ATTEMPTS:
427
444
  from datetime import timedelta
445
+
428
446
  locked_until = datetime.now() + timedelta(minutes=LOCKOUT_MINUTES)
429
- conn.execute("""
447
+ conn.execute(
448
+ """
430
449
  UPDATE users SET locked_until = ? WHERE id = ?
431
- """, (locked_until.isoformat(), user_id))
450
+ """,
451
+ (locked_until.isoformat(), user_id),
452
+ )
432
453
 
433
454
  conn.commit()
434
455
 
@@ -462,7 +483,7 @@ class UserManager:
462
483
  password=default_password,
463
484
  role=Role.ADMIN,
464
485
  tier=Tier.FREE, # Start FREE, upgrade when license is activated
465
- skip_password_validation=True
486
+ skip_password_validation=True,
466
487
  )
467
488
 
468
489
  if user is None:
@@ -488,7 +509,7 @@ class UserManager:
488
509
  tier: Tier,
489
510
  license_key: Optional[str] = None,
490
511
  expires_at: Optional[datetime] = None,
491
- _bypass_validation: bool = False
512
+ _bypass_validation: bool = False,
492
513
  ) -> tuple[bool, str]:
493
514
  """
494
515
  Set user's license tier.
@@ -510,10 +531,14 @@ class UserManager:
510
531
  if tier == Tier.PRO and not _bypass_validation:
511
532
  try:
512
533
  from souleyez.licensing import get_active_license
534
+
513
535
  license_info = get_active_license()
514
536
 
515
537
  if not license_info or not license_info.is_valid:
516
- return False, "Valid license required for Pro tier. Use 'souleyez license activate <key>'"
538
+ return (
539
+ False,
540
+ "Valid license required for Pro tier. Use 'souleyez license activate <key>'",
541
+ )
517
542
 
518
543
  # Use license info for expiration
519
544
  expires_at = license_info.expires_at
@@ -525,17 +550,20 @@ class UserManager:
525
550
 
526
551
  try:
527
552
  conn = self._get_conn()
528
- conn.execute("""
553
+ conn.execute(
554
+ """
529
555
  UPDATE users
530
556
  SET tier = ?, license_key = ?, license_expires_at = ?, updated_at = ?
531
557
  WHERE id = ?
532
- """, (
533
- tier.value,
534
- license_key,
535
- expires_at.isoformat() if expires_at else None,
536
- datetime.now().isoformat(),
537
- user_id
538
- ))
558
+ """,
559
+ (
560
+ tier.value,
561
+ license_key,
562
+ expires_at.isoformat() if expires_at else None,
563
+ datetime.now().isoformat(),
564
+ user_id,
565
+ ),
566
+ )
539
567
  conn.commit()
540
568
  conn.close()
541
569
  return True, ""
@@ -554,15 +582,14 @@ class UserManager:
554
582
  """
555
583
  try:
556
584
  conn = self._get_conn()
557
- cursor = conn.execute("""
585
+ cursor = conn.execute(
586
+ """
558
587
  UPDATE users
559
588
  SET tier = ?, license_key = NULL, license_expires_at = NULL, updated_at = ?
560
589
  WHERE tier = ?
561
- """, (
562
- Tier.FREE.value,
563
- datetime.now().isoformat(),
564
- Tier.PRO.value
565
- ))
590
+ """,
591
+ (Tier.FREE.value, datetime.now().isoformat(), Tier.PRO.value),
592
+ )
566
593
  count = cursor.rowcount
567
594
  conn.commit()
568
595
  conn.close()
@@ -577,21 +604,39 @@ class UserManager:
577
604
  def _row_to_user(self, row: sqlite3.Row) -> User:
578
605
  """Convert database row to User object."""
579
606
  user = User(
580
- id=row['id'],
581
- username=row['username'],
582
- email=row['email'],
583
- role=Role(row['role']),
584
- tier=Tier(row['tier']),
585
- is_active=bool(row['is_active']),
586
- created_at=datetime.fromisoformat(row['created_at']) if row['created_at'] else datetime.now(),
587
- updated_at=datetime.fromisoformat(row['updated_at']) if row['updated_at'] else datetime.now(),
588
- last_login=datetime.fromisoformat(row['last_login']) if row['last_login'] else None,
589
- license_key=row['license_key'],
590
- license_expires_at=datetime.fromisoformat(row['license_expires_at']) if row['license_expires_at'] else None,
591
- failed_login_attempts=row['failed_login_attempts'] or 0,
592
- locked_until=datetime.fromisoformat(row['locked_until']) if row['locked_until'] else None,
607
+ id=row["id"],
608
+ username=row["username"],
609
+ email=row["email"],
610
+ role=Role(row["role"]),
611
+ tier=Tier(row["tier"]),
612
+ is_active=bool(row["is_active"]),
613
+ created_at=(
614
+ datetime.fromisoformat(row["created_at"])
615
+ if row["created_at"]
616
+ else datetime.now()
617
+ ),
618
+ updated_at=(
619
+ datetime.fromisoformat(row["updated_at"])
620
+ if row["updated_at"]
621
+ else datetime.now()
622
+ ),
623
+ last_login=(
624
+ datetime.fromisoformat(row["last_login"]) if row["last_login"] else None
625
+ ),
626
+ license_key=row["license_key"],
627
+ license_expires_at=(
628
+ datetime.fromisoformat(row["license_expires_at"])
629
+ if row["license_expires_at"]
630
+ else None
631
+ ),
632
+ failed_login_attempts=row["failed_login_attempts"] or 0,
633
+ locked_until=(
634
+ datetime.fromisoformat(row["locked_until"])
635
+ if row["locked_until"]
636
+ else None
637
+ ),
593
638
  )
594
639
  # Set password fields (private)
595
- user._password_hash = row['password_hash']
596
- user._salt = row['salt']
640
+ user._password_hash = row["password_hash"]
641
+ user._salt = row["salt"]
597
642
  return user
@@ -7,6 +7,7 @@ Commands:
7
7
  - souleyez audit stats - Show audit statistics
8
8
  - souleyez audit export - Export audit logs
9
9
  """
10
+
10
11
  import click
11
12
  from datetime import datetime
12
13
  from rich.console import Console
@@ -38,11 +39,7 @@ def audit_list(limit, user, action):
38
39
  """List recent audit log entries."""
39
40
  logger = get_audit_logger()
40
41
 
41
- entries = logger.query(
42
- username=user,
43
- action=action,
44
- limit=limit
45
- )
42
+ entries = logger.query(username=user, action=action, limit=limit)
46
43
 
47
44
  if not entries:
48
45
  console.print("[yellow]No audit entries found[/yellow]")
@@ -58,45 +55,45 @@ def audit_list(limit, user, action):
58
55
 
59
56
  for e in entries:
60
57
  # Parse timestamp
61
- ts = e['timestamp'][:19].replace('T', ' ')
58
+ ts = e["timestamp"][:19].replace("T", " ")
62
59
 
63
60
  # Format resource
64
61
  resource = ""
65
- if e['resource_type']:
62
+ if e["resource_type"]:
66
63
  resource = f"{e['resource_type']}"
67
- if e['resource_id']:
64
+ if e["resource_id"]:
68
65
  resource += f":{e['resource_id']}"
69
66
 
70
67
  # Format details (truncate if long)
71
68
  details = ""
72
- if e['details']:
69
+ if e["details"]:
73
70
  try:
74
- d = json.loads(e['details'])
71
+ d = json.loads(e["details"])
75
72
  details = str(d)[:30]
76
73
  except:
77
- details = e['details'][:30]
74
+ details = e["details"][:30]
78
75
 
79
76
  # Success indicator
80
- success = "[green]✓[/green]" if e['success'] else "[red]✗[/red]"
77
+ success = "[green]✓[/green]" if e["success"] else "[red]✗[/red]"
81
78
 
82
79
  # Color action by category
83
- action_str = e['action']
84
- if action_str.startswith('auth.') or action_str.startswith('permission.'):
80
+ action_str = e["action"]
81
+ if action_str.startswith("auth.") or action_str.startswith("permission."):
85
82
  action_str = f"[red]{action_str}[/red]"
86
- elif action_str.startswith('user.'):
83
+ elif action_str.startswith("user."):
87
84
  action_str = f"[cyan]{action_str}[/cyan]"
88
- elif action_str.startswith('scan.'):
85
+ elif action_str.startswith("scan."):
89
86
  action_str = f"[yellow]{action_str}[/yellow]"
90
- elif action_str.startswith('engagement.'):
87
+ elif action_str.startswith("engagement."):
91
88
  action_str = f"[green]{action_str}[/green]"
92
89
 
93
90
  table.add_row(
94
91
  ts,
95
- e['username'] or '-',
92
+ e["username"] or "-",
96
93
  action_str,
97
- resource or '-',
98
- details or '-',
99
- success
94
+ resource or "-",
95
+ details or "-",
96
+ success,
100
97
  )
101
98
 
102
99
  console.print(table)
@@ -126,7 +123,7 @@ def audit_search(user, action, resource, start, end, failed, limit):
126
123
  start_date=start_date,
127
124
  end_date=end_date,
128
125
  success_only=not failed,
129
- limit=limit
126
+ limit=limit,
130
127
  )
131
128
 
132
129
  if not entries:
@@ -136,18 +133,18 @@ def audit_search(user, action, resource, start, end, failed, limit):
136
133
  console.print(f"[green]Found {len(entries)} entries[/green]\n")
137
134
 
138
135
  for e in entries:
139
- ts = e['timestamp'][:19].replace('T', ' ')
140
- success = "✓" if e['success'] else "✗"
136
+ ts = e["timestamp"][:19].replace("T", " ")
137
+ success = "✓" if e["success"] else "✗"
141
138
 
142
139
  console.print(f"[dim]{ts}[/dim] [{success}] [bold]{e['action']}[/bold]")
143
140
  console.print(f" User: {e['username'] or 'system'}")
144
141
 
145
- if e['resource_type']:
142
+ if e["resource_type"]:
146
143
  console.print(f" Resource: {e['resource_type']}:{e['resource_id'] or ''}")
147
144
 
148
- if e['details']:
145
+ if e["details"]:
149
146
  try:
150
- details = json.loads(e['details'])
147
+ details = json.loads(e["details"])
151
148
  console.print(f" Details: {details}")
152
149
  except:
153
150
  console.print(f" Details: {e['details']}")
@@ -164,22 +161,24 @@ def audit_stats(days):
164
161
  logger = get_audit_logger()
165
162
  stats = logger.get_stats(days)
166
163
 
167
- console.print(Panel(
168
- f"[bold]Period:[/bold] Last {stats['period_days']} days\n"
169
- f"[bold]Total Events:[/bold] {stats['total_events']}\n"
170
- f"[bold]Failed Events:[/bold] [red]{stats['failed_events']}[/red]\n"
171
- f"[bold]Unique Users:[/bold] {stats['unique_users']}",
172
- title="📊 Audit Statistics",
173
- border_style="blue"
174
- ))
164
+ console.print(
165
+ Panel(
166
+ f"[bold]Period:[/bold] Last {stats['period_days']} days\n"
167
+ f"[bold]Total Events:[/bold] {stats['total_events']}\n"
168
+ f"[bold]Failed Events:[/bold] [red]{stats['failed_events']}[/red]\n"
169
+ f"[bold]Unique Users:[/bold] {stats['unique_users']}",
170
+ title="📊 Audit Statistics",
171
+ border_style="blue",
172
+ )
173
+ )
175
174
 
176
- if stats['by_category']:
175
+ if stats["by_category"]:
177
176
  console.print("\n[bold]Events by Category:[/bold]")
178
177
  table = Table(show_header=False, box=None)
179
178
  table.add_column("Category", width=20)
180
179
  table.add_column("Count", justify="right")
181
180
 
182
- for cat, count in sorted(stats['by_category'].items(), key=lambda x: -x[1]):
181
+ for cat, count in sorted(stats["by_category"].items(), key=lambda x: -x[1]):
183
182
  bar = "█" * min(count // 10, 30)
184
183
  table.add_row(cat, f"{count} {bar}")
185
184
 
@@ -203,9 +202,7 @@ def audit_export(start, end, format, output):
203
202
  end_date = datetime.fromisoformat(end) if end else datetime.now()
204
203
 
205
204
  entries = logger.query(
206
- start_date=start_date,
207
- end_date=end_date,
208
- limit=10000 # Large limit for export
205
+ start_date=start_date, end_date=end_date, limit=10000 # Large limit for export
209
206
  )
210
207
 
211
208
  if not entries:
@@ -214,14 +211,14 @@ def audit_export(start, end, format, output):
214
211
 
215
212
  # Generate filename if not provided
216
213
  if not output:
217
- date_str = start_date.strftime('%Y%m%d')
214
+ date_str = start_date.strftime("%Y%m%d")
218
215
  output = f"audit_log_{date_str}.{format}"
219
216
 
220
217
  if format == "json":
221
- with open(output, 'w') as f:
218
+ with open(output, "w") as f:
222
219
  json.dump(entries, f, indent=2, default=str)
223
220
  else:
224
- with open(output, 'w', newline='') as f:
221
+ with open(output, "w", newline="") as f:
225
222
  writer = csv.DictWriter(f, fieldnames=entries[0].keys())
226
223
  writer.writeheader()
227
224
  writer.writerows(entries)
souleyez/commands/auth.py CHANGED
@@ -6,6 +6,7 @@ Commands:
6
6
  - souleyez logout - Log out of current session
7
7
  - souleyez whoami - Show current user info
8
8
  """
9
+
9
10
  import click
10
11
  import getpass
11
12
  from rich.console import Console
@@ -51,17 +52,21 @@ def login():
51
52
  # Check for first-run (no users exist)
52
53
  user_mgr = UserManager(get_db().db_path)
53
54
  if user_mgr.get_user_count() == 0:
54
- console.print("[yellow]No users found. Creating default admin account...[/yellow]")
55
+ console.print(
56
+ "[yellow]No users found. Creating default admin account...[/yellow]"
57
+ )
55
58
  created, password = user_mgr.ensure_default_admin()
56
59
  if created:
57
- console.print(Panel(
58
- f"[green]Default admin account created![/green]\n\n"
59
- f"Username: [bold]admin[/bold]\n"
60
- f"Password: [bold]{password}[/bold]\n\n"
61
- f"[red]⚠️ Save this password! It will not be shown again.[/red]",
62
- title="🔐 First Run Setup",
63
- border_style="green"
64
- ))
60
+ console.print(
61
+ Panel(
62
+ f"[green]Default admin account created![/green]\n\n"
63
+ f"Username: [bold]admin[/bold]\n"
64
+ f"Password: [bold]{password}[/bold]\n\n"
65
+ f"[red]⚠️ Save this password! It will not be shown again.[/red]",
66
+ title="🔐 First Run Setup",
67
+ border_style="green",
68
+ )
69
+ )
65
70
 
66
71
  # Prompt for credentials
67
72
  console.print("\n[bold]🔐 SoulEyez Login[/bold]\n")
@@ -88,7 +93,9 @@ def login():
88
93
  console.print(f"\n[green]✅ Welcome, {user.username}![/green]")
89
94
  console.print(f" Role: [cyan]{user.role.value.upper()}[/cyan]")
90
95
  console.print(f" Tier: [magenta]{tier_badge}[/magenta]")
91
- console.print(f" Session expires: {session.expires_at.strftime('%Y-%m-%d %H:%M')}\n")
96
+ console.print(
97
+ f" Session expires: {session.expires_at.strftime('%Y-%m-%d %H:%M')}\n"
98
+ )
92
99
 
93
100
 
94
101
  @click.command()
@@ -134,11 +141,21 @@ def whoami():
134
141
  table.add_row("Role", f"[cyan]{user.role.value.upper()}[/cyan]")
135
142
  table.add_row("Tier", f"[magenta]{tier_badge}[/magenta]")
136
143
  table.add_row("Email", user.email or "[dim]Not set[/dim]")
137
- table.add_row("Last Login", user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else "[dim]Never[/dim]")
138
- table.add_row("Account Status", "[green]Active[/green]" if user.is_active else "[red]Disabled[/red]")
144
+ table.add_row(
145
+ "Last Login",
146
+ (
147
+ user.last_login.strftime("%Y-%m-%d %H:%M")
148
+ if user.last_login
149
+ else "[dim]Never[/dim]"
150
+ ),
151
+ )
152
+ table.add_row(
153
+ "Account Status",
154
+ "[green]Active[/green]" if user.is_active else "[red]Disabled[/red]",
155
+ )
139
156
 
140
157
  if user.license_expires_at:
141
- table.add_row("License Expires", user.license_expires_at.strftime('%Y-%m-%d'))
158
+ table.add_row("License Expires", user.license_expires_at.strftime("%Y-%m-%d"))
142
159
 
143
160
  console.print(Panel(table, title="👤 Current User", border_style="blue"))
144
161
 
@@ -150,10 +167,13 @@ def _log_audit(action: str, user_id: str, username: str, details: str = None):
150
167
 
151
168
  try:
152
169
  conn = sqlite3.connect(get_db().db_path)
153
- conn.execute("""
170
+ conn.execute(
171
+ """
154
172
  INSERT INTO audit_log (user_id, username, action, details, timestamp)
155
173
  VALUES (?, ?, ?, ?, ?)
156
- """, (user_id, username, action, details, datetime.now().isoformat()))
174
+ """,
175
+ (user_id, username, action, details, datetime.now().isoformat()),
176
+ )
157
177
  conn.commit()
158
178
  conn.close()
159
179
  except Exception: