Skip to content

Commit f18c84c

Browse files
committed
feat: enhance export_release function to handle explicit local STIX base directory and improve error handling
1 parent 9a5c541 commit f18c84c

2 files changed

Lines changed: 44 additions & 4 deletions

File tree

mitreattack/attackToExcel/attackToExcel.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,19 @@ def export_release(
143143
expected_stix_versions = ", ".join(VALID_STIX_VERSIONS)
144144
raise ValueError(f"Invalid STIX version: {stix_version}. Expected one of: {expected_stix_versions}")
145145

146-
attack_version = normalize_attack_version(version or release_info.LATEST_VERSION)
146+
has_explicit_local_stix_base_dir = stix_base_dir is not None or os.environ.get("STIX_BASE_DIR") is not None
147+
attack_version = normalize_attack_version(version) if version else None
148+
release_version = attack_version or normalize_attack_version(release_info.LATEST_VERSION)
147149
release_domains = _validate_release_domains(domains)
148150
local_release_dir = Path(
149-
stix_base_dir or os.environ.get("STIX_BASE_DIR") or _default_release_dir(attack_version, stix_version)
151+
stix_base_dir or os.environ.get("STIX_BASE_DIR") or _default_release_dir(release_version, stix_version)
150152
)
151153
local_release_dir = local_release_dir.resolve()
152-
release_output_dir = Path(output_dir) / attack_version
154+
release_output_dir = (
155+
Path(output_dir)
156+
if has_explicit_local_stix_base_dir and attack_version is None
157+
else Path(output_dir) / release_version
158+
)
153159

154160
local_stix_files = {domain: _release_stix_file(local_release_dir, domain) for domain in release_domains}
155161
missing_domains = [domain for domain, stix_file in local_stix_files.items() if not stix_file.is_file()]
@@ -163,6 +169,13 @@ def export_release(
163169
)
164170
return
165171

172+
if attack_version is None:
173+
missing_domains_text = ", ".join(missing_domains)
174+
raise FileNotFoundError(
175+
f"Missing local STIX file(s) for domain(s): {missing_domains_text}. "
176+
"Pass --version to download missing ATT&CK release bundles."
177+
)
178+
166179
with tempfile.TemporaryDirectory() as temporary_directory:
167180
temporary_release_dir = _download_missing_release_domains(
168181
missing_domains=missing_domains,
@@ -186,7 +199,7 @@ def export_release(
186199

187200
def _export_release_domains(
188201
*,
189-
version: str,
202+
version: Optional[str],
190203
output_dir: Path,
191204
stix_files: Dict[str, Path],
192205
versioned_output_dir: bool,

tests/test_to_excel.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,33 @@ def fake_export(**kwargs):
167167
assert calls["exports"][0]["output_dir"] == str(tmp_path / "output" / "v19.0")
168168

169169

170+
def test_export_release_with_explicit_local_stix_base_dir_without_version_is_unversioned(tmp_path: Path, monkeypatch):
171+
"""Explicit local STIX bundle directories should not be labelled as ATT&CK releases unless a version is given."""
172+
stix_base_dir = tmp_path / "attack-releases" / "stix-2.0" / "attackwb"
173+
stix_base_dir.mkdir(parents=True)
174+
for domain in ["enterprise-attack", "mobile-attack"]:
175+
(stix_base_dir / f"{domain}.json").write_text("{}", encoding="utf-8")
176+
177+
calls = {}
178+
179+
def fake_export(**kwargs):
180+
calls.setdefault("exports", []).append(kwargs)
181+
182+
monkeypatch.setattr(attackToExcel, "export", fake_export)
183+
184+
attackToExcel.export_release(
185+
stix_base_dir=str(stix_base_dir),
186+
output_dir=str(tmp_path / "output" / "attackwb"),
187+
domains=["enterprise-attack", "mobile-attack"],
188+
)
189+
190+
assert [call["domain"] for call in calls["exports"]] == ["enterprise-attack", "mobile-attack"]
191+
assert calls["exports"][0]["version"] is None
192+
assert calls["exports"][0]["output_dir"] == str(tmp_path / "output" / "attackwb")
193+
assert calls["exports"][1]["version"] is None
194+
assert calls["exports"][1]["output_dir"] == str(tmp_path / "output" / "attackwb")
195+
196+
170197
def test_export_release_downloads_only_missing_domains_to_temporary_directory(tmp_path: Path, monkeypatch):
171198
"""Missing release STIX files should be downloaded per missing domain into a temporary tree."""
172199
stix_base_dir = tmp_path / "attack-releases" / "stix-2.0" / "v19.0"

0 commit comments

Comments
 (0)