maltego-transforms 1.0.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 (150) hide show
  1. maltego/_cli.py +443 -0
  2. maltego/_helper.py +64 -0
  3. maltego/auth/__init__.py +51 -0
  4. maltego/auth/claims.py +36 -0
  5. maltego/auth/dependency.py +480 -0
  6. maltego/auth/identity.py +110 -0
  7. maltego/auth/jwt_validator.py +236 -0
  8. maltego/auth/oidc_validator.py +118 -0
  9. maltego/auth/problem.py +198 -0
  10. maltego/auth/saml_validator.py +557 -0
  11. maltego/auth/settings.py +733 -0
  12. maltego/auth/validator.py +125 -0
  13. maltego/config.py +102 -0
  14. maltego/middlewares/middlewares.py +79 -0
  15. maltego/middlewares/oauth_middleware.py +213 -0
  16. maltego/middlewares/shared_settings_middleware.py +81 -0
  17. maltego/middlewares/user_concurrency_limit_middleware.py +113 -0
  18. maltego/middlewares/verify_metadata_middleware.py +72 -0
  19. maltego/model/__init__.py +113 -0
  20. maltego/model/context.py +678 -0
  21. maltego/model/entity/__init__.py +1675 -0
  22. maltego/model/entity/config.py +231 -0
  23. maltego/model/entity/constants.py +86 -0
  24. maltego/model/entity/display_info.py +26 -0
  25. maltego/model/entity/property.py +369 -0
  26. maltego/model/entity/type_handling.py +36 -0
  27. maltego/model/event.py +329 -0
  28. maltego/model/exception.py +182 -0
  29. maltego/model/graph.py +656 -0
  30. maltego/model/icon.py +9 -0
  31. maltego/model/input_constraints/__init__.py +35 -0
  32. maltego/model/input_constraints/base.py +288 -0
  33. maltego/model/input_constraints/entity/__init__.py +13 -0
  34. maltego/model/input_constraints/entity/composite.py +60 -0
  35. maltego/model/input_constraints/entity/property.py +81 -0
  36. maltego/model/input_constraints/entity/type.py +42 -0
  37. maltego/model/input_constraints/property/__init__.py +25 -0
  38. maltego/model/input_constraints/property/composite.py +64 -0
  39. maltego/model/input_constraints/property/equals.py +336 -0
  40. maltego/model/input_constraints/property/match.py +330 -0
  41. maltego/model/input_constraints/property/regex.py +221 -0
  42. maltego/model/link.py +369 -0
  43. maltego/model/machine.py +59 -0
  44. maltego/model/oauth.py +155 -0
  45. maltego/model/observer.py +53 -0
  46. maltego/model/prompt.py +305 -0
  47. maltego/model/server.py +952 -0
  48. maltego/model/transform/__init__.py +955 -0
  49. maltego/model/transform/setting.py +420 -0
  50. maltego/model/transform_input_annotation.py +322 -0
  51. maltego/model/transform_set.py +8 -0
  52. maltego/model/types.py +342 -0
  53. maltego/pagination/__init__.py +10 -0
  54. maltego/pagination/cursor_based_paginator.py +55 -0
  55. maltego/pagination/offset_limit_paginator.py +119 -0
  56. maltego/pagination/page_based_paginator.py +95 -0
  57. maltego/pagination/pagination.py +677 -0
  58. maltego/protocol/__init__.py +0 -0
  59. maltego/protocol/v3/__init__.py +0 -0
  60. maltego/protocol/v3/discovery/__init__.py +16 -0
  61. maltego/protocol/v3/discovery/auth.py +23 -0
  62. maltego/protocol/v3/discovery/capability.py +16 -0
  63. maltego/protocol/v3/discovery/entity.py +71 -0
  64. maltego/protocol/v3/discovery/hub_item.py +21 -0
  65. maltego/protocol/v3/discovery/icon.py +19 -0
  66. maltego/protocol/v3/discovery/machine.py +26 -0
  67. maltego/protocol/v3/discovery/seed.py +19 -0
  68. maltego/protocol/v3/discovery/status.py +15 -0
  69. maltego/protocol/v3/discovery/transform.py +71 -0
  70. maltego/protocol/v3/discovery/transform_set.py +12 -0
  71. maltego/protocol/v3/error.py +13 -0
  72. maltego/protocol/v3/execution/__init__.py +0 -0
  73. maltego/protocol/v3/execution/entity.py +46 -0
  74. maltego/protocol/v3/execution/graph.py +13 -0
  75. maltego/protocol/v3/execution/link.py +17 -0
  76. maltego/protocol/v3/execution/metadata.py +12 -0
  77. maltego/protocol/v3/execution/property.py +21 -0
  78. maltego/protocol/v3/execution/transform_run.py +208 -0
  79. maltego/protocol/v3/execution/ui_message.py +25 -0
  80. maltego/py.typed +0 -0
  81. maltego/runner/__init__.py +477 -0
  82. maltego/runner/transform_execution_context.py +620 -0
  83. maltego/runner/transform_result_set.py +261 -0
  84. maltego/server/__init__.py +1578 -0
  85. maltego/server/capability_matrix.py +432 -0
  86. maltego/server/etag_middleware.py +22 -0
  87. maltego/server/tracing_middleware.py +71 -0
  88. maltego/server/util.py +96 -0
  89. maltego/server/v3/__init__.py +1611 -0
  90. maltego/server/version.py +2 -0
  91. maltego/skills_assets/README.md +1 -0
  92. maltego/skills_assets/maltego-transform-basics/SKILL.md +27 -0
  93. maltego/skills_assets/maltego-transform-basics/references/transform-authoring-patterns.md +257 -0
  94. maltego/skills_assets/maltego-transform-build/SKILL.md +173 -0
  95. maltego/skills_assets/maltego-transform-design/SKILL.md +80 -0
  96. maltego/skills_assets/maltego-transform-design/references/standard-entity-selection.md +148 -0
  97. maltego/skills_assets/maltego-transform-discover/SKILL.md +91 -0
  98. maltego/skills_assets/maltego-transform-discover/references/direct-server-discovery.md +189 -0
  99. maltego/skills_assets/maltego-transform-discover/scripts/discover_server.py +97 -0
  100. maltego/skills_assets/maltego-transform-docs/SKILL.md +54 -0
  101. maltego/skills_assets/maltego-transform-docs/references/docs-routing.md +58 -0
  102. maltego/skills_assets/maltego-transform-skill-index/SKILL.md +60 -0
  103. maltego/skills_assets/maltego-transform-test/SKILL.md +214 -0
  104. maltego/skills_assets/maltego-transform-test/references/testing-and-parity.md +300 -0
  105. maltego/skills_assets/maltego-transform-test/scripts/README.md +29 -0
  106. maltego/skills_assets/maltego-transform-test/scripts/sdk_project_check.py +283 -0
  107. maltego/skills_assets/maltego-transforms/SKILL.md +53 -0
  108. maltego/skills_assets/maltego-trx-migration-implementer/SKILL.md +244 -0
  109. maltego/skills_assets/maltego-trx-migration-implementer/references/trx-to-sdk-mapping.md +177 -0
  110. maltego/skills_assets/maltego-trx-migration-implementer/scripts/README.md +42 -0
  111. maltego/skills_assets/maltego-trx-migration-implementer/scripts/trx_to_sdk_candidates.py +223 -0
  112. maltego/skills_assets/maltego-trx-migration-planner/SKILL.md +230 -0
  113. maltego/skills_assets/maltego-trx-migration-planner/references/known-trx-examples.md +96 -0
  114. maltego/skills_assets/maltego-trx-migration-planner/references/standard-entity-selection.md +148 -0
  115. maltego/skills_assets/maltego-trx-migration-planner/references/trx-risk-taxonomy.md +142 -0
  116. maltego/skills_assets/maltego-trx-migration-planner/references/trx-to-sdk-mapping.md +177 -0
  117. maltego/skills_assets/maltego-trx-migration-planner/scripts/README.md +76 -0
  118. maltego/skills_assets/maltego-trx-migration-planner/scripts/std_entity_lookup.py +142 -0
  119. maltego/skills_assets/maltego-trx-migration-planner/scripts/trx_contract.py +312 -0
  120. maltego/skills_assets/maltego-trx-migration-planner/scripts/trx_inventory.py +297 -0
  121. maltego/skills_assets/maltego-trx-migration-planner/scripts/trx_migration_report.py +222 -0
  122. maltego/template_dir/README.md +105 -0
  123. maltego/template_dir/__init__.py +0 -0
  124. maltego/template_dir/project.py +34 -0
  125. maltego/template_dir/requirements.txt +2 -0
  126. maltego/template_dir/transforms/__init__.py +0 -0
  127. maltego/template_dir/transforms/entity_features_example.py +217 -0
  128. maltego/template_dir/transforms/error_handling_example.py +206 -0
  129. maltego/template_dir/transforms/input_constraints_example.py +262 -0
  130. maltego/template_dir/transforms/logging_example.py +120 -0
  131. maltego/template_dir/transforms/middleware_example.py +197 -0
  132. maltego/template_dir/transforms/pagination_example.py +325 -0
  133. maltego/template_dir/transforms/prompts_example.py +373 -0
  134. maltego/template_dir/transforms/quickstart_example.py +349 -0
  135. maltego/template_dir/transforms/transform_settings_example.py +481 -0
  136. maltego/template_minimal_dir/README.md +84 -0
  137. maltego/template_minimal_dir/__init__.py +1 -0
  138. maltego/template_minimal_dir/project.py +21 -0
  139. maltego/template_minimal_dir/requirements.txt +2 -0
  140. maltego/template_minimal_dir/transforms/__init__.py +1 -0
  141. maltego/tracing/__init__.py +133 -0
  142. maltego/util/__init__.py +568 -0
  143. maltego/util/collections_utils.py +8 -0
  144. maltego/util/trace_context.py +3 -0
  145. maltego/util/typing.py +23 -0
  146. maltego_transforms-1.0.0.dist-info/LICENSE +21 -0
  147. maltego_transforms-1.0.0.dist-info/METADATA +189 -0
  148. maltego_transforms-1.0.0.dist-info/RECORD +150 -0
  149. maltego_transforms-1.0.0.dist-info/WHEEL +4 -0
  150. maltego_transforms-1.0.0.dist-info/entry_points.txt +3 -0
maltego/_cli.py ADDED
@@ -0,0 +1,443 @@
1
+ import os
2
+ import re
3
+ import shutil
4
+ import sys
5
+ from typing import List, Optional, Tuple
6
+
7
+ import maltego
8
+
9
+ """
10
+ Receive commands run to start a new project.
11
+ """
12
+ FORCE_OVERWRITE = False
13
+ DEMO_TEMPLATE = "template_dir"
14
+ MINIMAL_TEMPLATE = "template_minimal_dir"
15
+ DEFAULT_PROJECT_MANAGER = "bare"
16
+ PROJECT_MANAGERS = {"bare", "poetry", "uv"}
17
+ START_USAGE = """Usage:
18
+ maltego-transforms start [options] <project_name>
19
+
20
+ Create a new Maltego Transform SDK project.
21
+
22
+ Options:
23
+ --minimal Generate the compact starter template.
24
+ --demo Generate the example-rich starter template (default).
25
+ --project-manager bare|poetry|uv Add project metadata for the selected tool.
26
+ --with-skills Add local SDK agent skills under .agents/skills/.
27
+ --skills-scope local|global Install skills into the project (local) or your global
28
+ agent skills directory. Implies --with-skills.
29
+
30
+ Examples:
31
+ maltego-transforms start my_transforms
32
+ maltego-transforms start --minimal --with-skills --project-manager uv my_transforms
33
+
34
+ Add SDK skills to an existing project:
35
+ maltego-transforms install-skills --target .
36
+ """
37
+ INIT_USAGE = """Usage:
38
+ maltego-transforms init [options]
39
+
40
+ Initialize the current directory as a Maltego Transform SDK project.
41
+
42
+ Options:
43
+ --minimal Generate the compact starter template.
44
+ --demo Generate the example-rich starter template (default).
45
+ --project-manager bare|poetry|uv Add project metadata for the selected tool.
46
+ --with-skills Add local SDK agent skills under .agents/skills/.
47
+ --skills-scope local|global Install skills into the project (local) or your global
48
+ agent skills directory. Implies --with-skills.
49
+
50
+ Examples:
51
+ maltego-transforms init --minimal --with-skills
52
+ maltego-transforms init --project-manager poetry
53
+ """
54
+ INSTALL_SKILLS_USAGE = """Usage:
55
+ maltego-transforms install-skills [--scope local|global] [--target DIR]
56
+
57
+ Install Maltego SDK agent skills without generating or changing starter files.
58
+
59
+ Options:
60
+ --target DIR Existing project directory to receive local skills.
61
+ Defaults to the current directory.
62
+ --scope local|global local installs into DIR/.agents/skills and adds
63
+ project bootstrap notes. global installs into the
64
+ configured global agent skills directory.
65
+
66
+ Examples:
67
+ maltego-transforms install-skills --target .
68
+ maltego-transforms install-skills --scope global
69
+ """
70
+ COMMAND_USAGE = f"""Usage:
71
+ maltego-transforms <command> [options]
72
+
73
+ Commands:
74
+ start Create a new SDK project.
75
+ init Initialize the current directory as an SDK project.
76
+ install-skills Add SDK agent skills to an existing project or global skills directory.
77
+
78
+ Run `maltego-transforms <command> --help` for command-specific options.
79
+ """
80
+ LOCAL_SKILLS_BOOTSTRAP_MARKER = "<!-- maltego-transforms-skills -->"
81
+ LOCAL_SKILLS_BOOTSTRAP = f"""{LOCAL_SKILLS_BOOTSTRAP_MARKER}
82
+
83
+ ## Maltego Transform SDK Skills
84
+
85
+ This project includes local Maltego SDK skills under `.agents/skills`.
86
+ When working on transforms in this project, first read:
87
+
88
+ `.agents/skills/maltego-transform-skill-index/SKILL.md`
89
+
90
+ That index routes agents to focused skills for SDK v3 authoring, TRX migration,
91
+ server discovery, docs lookup, and testing.
92
+ """
93
+ LOCAL_SKILLS_README = """# Maltego Transform SDK Skills
94
+
95
+ This project includes local skills in `.agents/skills`.
96
+
97
+ Agents that support project-local skills can load them when started from this
98
+ project directory. If skills are not auto-discovered, read:
99
+
100
+ `.agents/skills/maltego-transform-skill-index/SKILL.md`
101
+ """
102
+
103
+
104
+ def execute_from_command_line() -> None:
105
+ args = sys.argv[1:]
106
+ try:
107
+ args, with_skills, skills_scope = parse_skills_options(args)
108
+ except ValueError as e:
109
+ print(f"ERROR: {e}")
110
+ return
111
+
112
+ if not args:
113
+ print(COMMAND_USAGE)
114
+ elif args[0].lower() == "start":
115
+ run_start(args[1:], with_skills=with_skills, skills_scope=skills_scope)
116
+ elif args[0].lower() == "init":
117
+ run_start(args[1:], create_dir=False, with_skills=with_skills,
118
+ skills_scope=skills_scope)
119
+ elif args[0].lower() == "install-skills":
120
+ run_install_skills(args[1:])
121
+ else:
122
+ print(COMMAND_USAGE)
123
+
124
+
125
+ def parse_skills_options(args: List[str]) -> Tuple[List[str], bool, str]:
126
+ """Extract skills-related CLI options from argv-style input."""
127
+ cleaned_args: List[str] = []
128
+ with_skills = False
129
+ skills_scope = "local"
130
+ i = 0
131
+ while i < len(args):
132
+ arg = args[i]
133
+ if arg == "--with-skills":
134
+ with_skills = True
135
+ elif arg == "--skills-scope":
136
+ if i + 1 >= len(args):
137
+ raise ValueError("--skills-scope requires 'local' or 'global'")
138
+ skills_scope = args[i + 1].lower()
139
+ with_skills = True
140
+ i += 1
141
+ elif arg.startswith("--skills-scope="):
142
+ skills_scope = arg.split("=", 1)[1].lower()
143
+ with_skills = True
144
+ else:
145
+ cleaned_args.append(arg)
146
+ i += 1
147
+
148
+ if skills_scope not in {"local", "global"}:
149
+ raise ValueError("--skills-scope must be 'local' or 'global'")
150
+ return cleaned_args, with_skills, skills_scope
151
+
152
+
153
+ def find_template_dir(template_name: str = DEMO_TEMPLATE) -> str:
154
+ """Find a project template directory in the maltego namespace paths."""
155
+ for path in maltego.__path__:
156
+ template_path = os.path.join(path, template_name)
157
+ if os.path.isdir(template_path):
158
+ return template_path
159
+ raise FileNotFoundError(f"Could not find {template_name} in maltego package paths")
160
+
161
+
162
+ def parse_start_args(args: List[str], create_dir: bool) -> Optional[Tuple[Optional[str], str, str]]:
163
+ template_name = DEMO_TEMPLATE
164
+ project_manager = DEFAULT_PROJECT_MANAGER
165
+ project_args = []
166
+ index = 0
167
+
168
+ while index < len(args):
169
+ arg = args[index]
170
+ if arg in {"-h", "--help"}:
171
+ print(START_USAGE if create_dir else INIT_USAGE)
172
+ return None
173
+ if arg == "--minimal":
174
+ template_name = MINIMAL_TEMPLATE
175
+ elif arg == "--demo":
176
+ template_name = DEMO_TEMPLATE
177
+ elif arg == "--project-manager":
178
+ index += 1
179
+ if index >= len(args):
180
+ print(START_USAGE if create_dir else INIT_USAGE)
181
+ sys.exit(2)
182
+ project_manager = args[index].lower()
183
+ if project_manager not in PROJECT_MANAGERS:
184
+ print(START_USAGE if create_dir else INIT_USAGE)
185
+ sys.exit(2)
186
+ else:
187
+ project_args.append(arg)
188
+ index += 1
189
+
190
+ if create_dir:
191
+ if len(project_args) != 1:
192
+ print(START_USAGE)
193
+ return None
194
+ if not is_safe_project_directory_name(project_args[0]):
195
+ print("Project name must be a simple directory name without path separators.")
196
+ return None
197
+ if project_manager != DEFAULT_PROJECT_MANAGER and not normalize_project_name(project_args[0]):
198
+ print(
199
+ "Project name must contain at least one alphanumeric character "
200
+ "for a valid pyproject name."
201
+ )
202
+ sys.exit(2)
203
+ return project_args[0], template_name, project_manager
204
+
205
+ if project_args:
206
+ print(INIT_USAGE)
207
+ return None
208
+ return None, template_name, project_manager
209
+
210
+
211
+ def normalize_project_name(project_name: str) -> str:
212
+ normalized_name = re.sub(r"[^a-z0-9]+", "-", project_name.lower())
213
+ return normalized_name.strip("-")
214
+
215
+
216
+ def is_safe_project_directory_name(project_name: str) -> bool:
217
+ return (
218
+ bool(project_name)
219
+ and project_name not in {".", ".."}
220
+ and not project_name.startswith("-")
221
+ and not os.path.isabs(project_name)
222
+ and "/" not in project_name
223
+ and "\\" not in project_name
224
+ )
225
+
226
+
227
+ def render_pyproject(project_name: str, project_manager: str) -> Optional[str]:
228
+ if project_manager == DEFAULT_PROJECT_MANAGER:
229
+ return None
230
+
231
+ normalized_name = normalize_project_name(project_name)
232
+ pyproject_lines = [
233
+ "[project]",
234
+ f'name = "{normalized_name}"',
235
+ 'version = "0.1.0"',
236
+ 'requires-python = ">=3.10"',
237
+ "dependencies = [",
238
+ ' "maltego-transforms>=1.0.0",',
239
+ ' "maltego-transforms-std-entities>=1.0.0",',
240
+ "]",
241
+ ]
242
+
243
+ if project_manager == "poetry":
244
+ pyproject_lines.extend(["", "[tool.poetry]", "package-mode = false"])
245
+
246
+ return "\n".join(pyproject_lines) + "\n"
247
+
248
+
249
+ def apply_project_manager(project_dir: str, project_name: str, project_manager: str) -> None:
250
+ pyproject_text = render_pyproject(project_name, project_manager)
251
+ if pyproject_text is None:
252
+ return
253
+
254
+ pyproject_path = os.path.join(project_dir, "pyproject.toml")
255
+ with open(pyproject_path, "w", encoding="utf-8") as pyproject_file:
256
+ pyproject_file.write(pyproject_text)
257
+
258
+
259
+ def find_skills_dir() -> str:
260
+ """Find skills_assets in the maltego namespace paths."""
261
+ for path in maltego.__path__:
262
+ skills_path = os.path.join(path, "skills_assets")
263
+ if os.path.isdir(skills_path):
264
+ return skills_path
265
+ raise FileNotFoundError("Could not find skills_assets in maltego package paths")
266
+
267
+
268
+ def find_global_skills_dir(skills_home: str | None = None) -> str:
269
+ """Return the provider-agnostic global skills directory."""
270
+ if skills_home:
271
+ return skills_home
272
+ return os.path.join(os.path.expanduser("~"), ".agents", "skills")
273
+
274
+
275
+ def write_local_skills_bootstrap(project_dir: str) -> None:
276
+ """Create local pointers that tell agents how to load generated skills."""
277
+ agents_dir = os.path.join(project_dir, ".agents")
278
+ os.makedirs(agents_dir, exist_ok=True)
279
+
280
+ readme_path = os.path.join(agents_dir, "README.md")
281
+ with open(readme_path, "w", encoding="utf-8") as f:
282
+ f.write(LOCAL_SKILLS_README)
283
+
284
+ agents_md_path = os.path.join(project_dir, "AGENTS.md")
285
+ if os.path.exists(agents_md_path):
286
+ with open(agents_md_path, encoding="utf-8") as f:
287
+ existing = f.read()
288
+ if LOCAL_SKILLS_BOOTSTRAP_MARKER in existing:
289
+ return
290
+ separator = "" if existing.endswith("\n") else "\n"
291
+ with open(agents_md_path, "a", encoding="utf-8") as f:
292
+ f.write(f"{separator}\n{LOCAL_SKILLS_BOOTSTRAP}")
293
+ else:
294
+ with open(agents_md_path, "w", encoding="utf-8") as f:
295
+ f.write(f"# Agent Guide\n\n{LOCAL_SKILLS_BOOTSTRAP}")
296
+
297
+
298
+ def install_skills(project_dir: str, skills_scope: str = "local", skills_home: str | None = None) -> str:
299
+ """Install SDK agent skills into an existing project or global skills directory."""
300
+ if skills_scope not in {"local", "global"}:
301
+ raise ValueError("skills_scope must be 'local' or 'global'")
302
+
303
+ skills_dir_path = find_skills_dir()
304
+ if skills_scope == "global":
305
+ skills_dst = find_global_skills_dir(skills_home)
306
+ else:
307
+ skills_dst = os.path.join(project_dir, ".agents", "skills")
308
+ os.makedirs(skills_dst, exist_ok=True)
309
+ copytree(src=skills_dir_path, dst=skills_dst, dirs_exist_ok=True)
310
+ if skills_scope == "local":
311
+ write_local_skills_bootstrap(project_dir)
312
+ return skills_dst
313
+
314
+
315
+ def parse_install_skills_args(args: List[str]) -> Tuple[str, str]:
316
+ """Parse install-skills arguments into (scope, target directory)."""
317
+ skills_scope = "local"
318
+ target_dir = os.getcwd()
319
+ index = 0
320
+ while index < len(args):
321
+ arg = args[index]
322
+ if arg in {"-h", "--help"}:
323
+ print(INSTALL_SKILLS_USAGE)
324
+ sys.exit(0)
325
+ if arg == "--scope":
326
+ index += 1
327
+ if index >= len(args):
328
+ print(INSTALL_SKILLS_USAGE)
329
+ sys.exit(2)
330
+ skills_scope = args[index].lower()
331
+ elif arg.startswith("--scope="):
332
+ skills_scope = arg.split("=", 1)[1].lower()
333
+ elif arg == "--target":
334
+ index += 1
335
+ if index >= len(args):
336
+ print(INSTALL_SKILLS_USAGE)
337
+ sys.exit(2)
338
+ target_dir = args[index]
339
+ elif arg.startswith("--target="):
340
+ target_dir = arg.split("=", 1)[1]
341
+ else:
342
+ print(INSTALL_SKILLS_USAGE)
343
+ sys.exit(2)
344
+ index += 1
345
+
346
+ if skills_scope not in {"local", "global"}:
347
+ print(INSTALL_SKILLS_USAGE)
348
+ sys.exit(2)
349
+ return skills_scope, target_dir
350
+
351
+
352
+ def run_install_skills(args: List[str], skills_home: str | None = None) -> None:
353
+ """Install SDK skills without generating a starter project."""
354
+ skills_scope, target_dir = parse_install_skills_args(args)
355
+ project_dir = os.path.abspath(target_dir)
356
+ if skills_scope == "local" and not os.path.isdir(project_dir):
357
+ print(f"ERROR: target directory does not exist: {target_dir}")
358
+ sys.exit(2)
359
+
360
+ skills_dst = install_skills(project_dir, skills_scope, skills_home)
361
+ if skills_scope == "global":
362
+ print(f"Successfully installed SDK skills to '{skills_dst}'.")
363
+ print("Agents that use this global skills directory can load the Maltego SDK skills from any project.")
364
+ else:
365
+ print(f"Successfully added SDK skills to '{skills_dst}'.")
366
+ print("Agents that start in this project directory can load them from '.agents/skills/'.")
367
+ print("If skills are not auto-discovered, read '.agents/skills/maltego-transform-skill-index/SKILL.md' first.")
368
+
369
+
370
+ def run_start(
371
+ args: List[str],
372
+ create_dir: bool = True,
373
+ with_skills: bool = False,
374
+ skills_scope: str = "local",
375
+ skills_home: str | None = None,
376
+ ) -> None:
377
+ if skills_scope not in {"local", "global"}:
378
+ raise ValueError("skills_scope must be 'local' or 'global'")
379
+
380
+ parsed_args = parse_start_args(args, create_dir)
381
+ if parsed_args is None:
382
+ return
383
+
384
+ project, template_name, project_manager = parsed_args
385
+ if create_dir:
386
+ assert project is not None
387
+ project_dir = os.path.join(os.getcwd(), project)
388
+ # Confine the generated project to the current working directory; reject
389
+ # names that escape it (e.g. absolute paths or "../").
390
+ cwd = os.path.abspath(os.getcwd())
391
+ if os.path.commonpath([cwd, os.path.abspath(project_dir)]) != cwd:
392
+ print(f"ERROR: project name must stay within the current directory: {project!r}")
393
+ sys.exit(1)
394
+ try:
395
+ os.makedirs(project_dir)
396
+ except FileExistsError:
397
+ if not FORCE_OVERWRITE:
398
+ print(f"Project directory {project_dir} already exists. Overwrite? [y/n]", end=" ")
399
+ choice = input().lower()
400
+ if choice in ['yes', 'y']:
401
+ pass
402
+ else:
403
+ sys.exit(1)
404
+ else:
405
+ project_dir = os.getcwd()
406
+ project = os.path.basename(project_dir)
407
+ if project_manager != DEFAULT_PROJECT_MANAGER and not normalize_project_name(project):
408
+ print(
409
+ "Project name must contain at least one alphanumeric character "
410
+ "for a valid pyproject name."
411
+ )
412
+ sys.exit(2)
413
+ try:
414
+ template_dir_path = find_template_dir(template_name)
415
+ copytree(src=template_dir_path, dst=project_dir, dirs_exist_ok=True)
416
+ apply_project_manager(project_dir, project, project_manager)
417
+ print(f"Successfully created a new project in the '{project}' folder.")
418
+
419
+ if with_skills:
420
+ skills_dst = install_skills(project_dir, skills_scope, skills_home)
421
+ if skills_scope == "global":
422
+ print(f"Successfully installed SDK skills to '{skills_dst}'.")
423
+ print("Agents that use this global skills directory can load the Maltego SDK skills from any project.")
424
+ else:
425
+ print("Successfully added SDK skills to '.agents/skills/'.")
426
+ print("Agents that start in this project directory can load them from '.agents/skills/'.")
427
+ print("If skills are not auto-discovered, read '.agents/skills/maltego-transform-skill-index/SKILL.md' first.")
428
+ except FileExistsError:
429
+ print(f"ERROR: '{project_dir}' already exists")
430
+ except OSError as e:
431
+ print(f"ERROR: {e}")
432
+
433
+
434
+ def copytree(src: str, dst: str, symlinks: bool = False, dirs_exist_ok: bool = False) -> None:
435
+ os.makedirs(dst, exist_ok=dirs_exist_ok)
436
+ for item in os.listdir(src):
437
+ if "__pycache__" not in item and ".pyc" not in item and item != ".gitkeep":
438
+ source_path = os.path.join(src, item)
439
+ dst_path = os.path.join(dst, item)
440
+ if os.path.isdir(source_path):
441
+ copytree(src=source_path, dst=dst_path, symlinks=symlinks, dirs_exist_ok=dirs_exist_ok)
442
+ else:
443
+ shutil.copy2(source_path, dst_path)
maltego/_helper.py ADDED
@@ -0,0 +1,64 @@
1
+ from typing import Any, Optional, Tuple, List
2
+ import uuid
3
+ import re
4
+
5
+
6
+ MAJOR_VERSION_REGEX_GROUP_NAME = "major"
7
+ MINOR_VERSION_REGEX_GROUP_NAME = "minor"
8
+ PATCH_VERSION_REGEX_GROUP_NAME = "patch"
9
+ PRERELEASE_VERSION_REGEX_GROUP_NAME = "prerelease"
10
+ BUILD_META_VERSION_REGEX_GROUP_NAME = "buildmetadata"
11
+ EXTRA_REGEX_GROUP_NAME = "extra"
12
+ USER_AGENT_PATTERN = re.compile(
13
+ r"^Maltego Desktop\/(?P<" +
14
+ MAJOR_VERSION_REGEX_GROUP_NAME + r">0|[1-9]\d*)\.(?P<" +
15
+ MINOR_VERSION_REGEX_GROUP_NAME + r">0|[1-9]\d*)\.(?P<" +
16
+ PATCH_VERSION_REGEX_GROUP_NAME + r">0|[1-9]\d*)(?:-(?P<" +
17
+ PRERELEASE_VERSION_REGEX_GROUP_NAME + r">(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<" + # pylint: disable=line-too-long
18
+ BUILD_META_VERSION_REGEX_GROUP_NAME + r">[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? \((?P<" +
19
+ EXTRA_REGEX_GROUP_NAME + r">[^)]+)\)?$"
20
+ )
21
+
22
+
23
+ def create_maltego_id() -> str:
24
+ # Generate a new UUID
25
+ guid: uuid.UUID = uuid.uuid4()
26
+ # Convert the UUID to an integer and mask the lower 64 bits
27
+ guid_int: int = int(guid.int) & ((1 << 64) - 1)
28
+ # Define the base36 alphabet
29
+ alphabet: str = "0123456789abcdefghijklmnopqrstuvwxyz"
30
+ # Convert the integer to a base36 string
31
+ base36: str = ''
32
+ while guid_int != 0:
33
+ guid_int, i = divmod(guid_int, len(alphabet))
34
+ base36 = alphabet[i] + base36
35
+ # Return the base36 string
36
+
37
+ return base36
38
+
39
+
40
+ def __parse_group(matches: re.Match[str], groupname: str, target_type: type) -> Any:
41
+ try:
42
+ return target_type(matches.group(groupname))
43
+ except ValueError:
44
+ return None
45
+
46
+
47
+ def parse_ua(
48
+ user_agent_string: Optional[str]
49
+ ) -> Tuple[Optional[int], Optional[int], Optional[int], Optional[str], Optional[str], Optional[List[str]]]:
50
+ matches = USER_AGENT_PATTERN.fullmatch(user_agent_string) if user_agent_string else None
51
+ if matches is not None:
52
+ major_version = __parse_group(matches, MAJOR_VERSION_REGEX_GROUP_NAME, int)
53
+ minor_version = __parse_group(matches, MINOR_VERSION_REGEX_GROUP_NAME, int)
54
+ patch_version = __parse_group(matches, PATCH_VERSION_REGEX_GROUP_NAME, int)
55
+ prerelease = __parse_group(matches, PRERELEASE_VERSION_REGEX_GROUP_NAME, str)
56
+ build_metadata = __parse_group(matches, BUILD_META_VERSION_REGEX_GROUP_NAME, str)
57
+ extra_str = __parse_group(matches, EXTRA_REGEX_GROUP_NAME, str)
58
+ extra = None
59
+ if isinstance(extra_str, str):
60
+ extra = [extra.strip() for extra in extra_str.split(";")]
61
+ else:
62
+ extra = None
63
+ return (major_version, minor_version, patch_version, prerelease, build_metadata, extra)
64
+ return (None, None, None, None, None, None)
@@ -0,0 +1,51 @@
1
+ # Copyright (c) Maltego Technologies GmbH.
2
+ """
3
+ Maltego authentication module.
4
+
5
+ Provides optional JWT/OIDC authentication for Maltego transform servers.
6
+ """
7
+
8
+ from maltego.auth.dependency import close_validator, optional_auth, optional_bearer
9
+ from maltego.auth.identity import AuthContext, Identity
10
+ from maltego.auth.jwt_validator import JWTTokenValidator
11
+ from maltego.auth.oidc_validator import OIDCTokenValidator
12
+ from maltego.auth.saml_validator import SAMLTokenValidator
13
+ from maltego.auth.settings import (
14
+ AuthMode,
15
+ AuthProviderType,
16
+ AuthTokenOrigin,
17
+ AuthSettings,
18
+ get_auth_settings,
19
+ set_auth_settings,
20
+ reset_auth_settings,
21
+ )
22
+ from maltego.auth.validator import AuthValidationFailure, AuthValidationSuccess, TokenValidator, ValidationErrorKind
23
+ from maltego.auth.problem import MaltegoAuthErrorCode, MaltegoAuthProblemDetail, AuthProblemException
24
+
25
+ __all__ = [
26
+ # Dependencies
27
+ "optional_auth",
28
+ "optional_bearer",
29
+ "close_validator",
30
+ # Settings
31
+ "AuthMode",
32
+ "AuthProviderType",
33
+ "AuthTokenOrigin",
34
+ "AuthSettings",
35
+ "get_auth_settings",
36
+ "set_auth_settings",
37
+ "reset_auth_settings",
38
+ # Validation
39
+ "AuthValidationFailure",
40
+ "AuthValidationSuccess",
41
+ "JWTTokenValidator",
42
+ "SAMLTokenValidator",
43
+ "TokenValidator",
44
+ "OIDCTokenValidator",
45
+ "ValidationErrorKind",
46
+ "MaltegoAuthErrorCode",
47
+ "MaltegoAuthProblemDetail",
48
+ "AuthProblemException",
49
+ "AuthContext",
50
+ "Identity",
51
+ ]
maltego/auth/claims.py ADDED
@@ -0,0 +1,36 @@
1
+ # Copyright (c) Maltego Technologies GmbH.
2
+ """JWT claim helpers."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+ import binascii
8
+ import json
9
+ import re
10
+ from typing import Any, Optional
11
+
12
+ _BASE64URL_SEGMENT = re.compile(r"^[A-Za-z0-9_-]*$")
13
+
14
+
15
+ def _decode_base64url_json(segment: str) -> Optional[Any]:
16
+ if not segment:
17
+ return None
18
+ try:
19
+ padding = "=" * (-len(segment) % 4)
20
+ decoded = base64.urlsafe_b64decode(f"{segment}{padding}".encode("ascii"))
21
+ return json.loads(decoded.decode("utf-8"))
22
+ except (binascii.Error, UnicodeDecodeError, ValueError):
23
+ return None
24
+
25
+
26
+ def decode_unverified_jwt_claims(token: str) -> Optional[dict[str, Any]]:
27
+ """Decode compact JWT payload claims without signature or claim validation."""
28
+ parts = token.split(".")
29
+ if len(parts) != 3 or any(_BASE64URL_SEGMENT.fullmatch(part) is None for part in parts):
30
+ return None
31
+
32
+ header = _decode_base64url_json(parts[0])
33
+ payload = _decode_base64url_json(parts[1])
34
+ if not isinstance(header, dict) or not isinstance(payload, dict):
35
+ return None
36
+ return payload