mirror of
https://github.com/laramies/theHarvester.git
synced 2026-08-17 19:35:40 +02:00
* feat: add HarvestView operator UI * Use JSONL-only HarvestView file flows * Drive HarvestView activities from the API catalog * Add result action controls * Render shared DNS resolver defaults * Clarify resolver cardinality in HarvestView * Load Tabulator from CDNjs * Fix HarvestView wiki link * Align HarvestView with execution status contract * Add HarvestView import and action controls * Harden HarvestView browser assertions * Simplify HarvestView run selection * Show truthful HarvestView execution outcomes * refactor: show canonical URL results in HarvestView * Show canonical hostname results in HarvestView * test(harvestview): remove browser error race
136 lines
6.1 KiB
Python
136 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import ipaddress
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
def test_harvestview_owns_root_and_issues_an_http_only_session(tmp_path, monkeypatch) -> None:
|
|
from theHarvester.lib.api import api
|
|
from theHarvester.lib.resolver_selection import DEFAULT_DNS_RESOLVERS
|
|
|
|
monkeypatch.setenv('THEHARVESTER_API_KEY', 'test-key')
|
|
monkeypatch.setenv('THEHARVESTER_RUN_DB', str(tmp_path / 'runs.sqlite'))
|
|
monkeypatch.setenv('THEHARVESTER_RUN_WORKER', 'disabled')
|
|
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('127.0.0.1', 50000)) as client:
|
|
root = client.get('/')
|
|
legacy = client.get('/app')
|
|
runs = client.get('/api/v1/runs')
|
|
|
|
assert root.status_code == 200
|
|
assert '<title>HarvestView</title>' in root.text
|
|
assert f'value="{",".join(DEFAULT_DNS_RESOLVERS)}"' in root.text
|
|
assert 'Resolve with the configured resolver addresses.' in root.text
|
|
assert legacy.status_code == 404
|
|
cookie = root.headers['set-cookie']
|
|
assert 'theharvester-api-key=' in cookie
|
|
assert 'test-key' not in cookie
|
|
assert 'HttpOnly' in cookie
|
|
assert 'SameSite=strict' in cookie
|
|
assert 'Path=/api/v1' in cookie
|
|
assert runs.status_code == 200
|
|
assert runs.json() == []
|
|
|
|
|
|
def test_harvestview_assets_load_outside_the_repository_directory(tmp_path, monkeypatch) -> None:
|
|
from theHarvester.lib.api import api
|
|
|
|
monkeypatch.setenv('THEHARVESTER_API_KEY', 'test-key')
|
|
monkeypatch.setenv('THEHARVESTER_RUN_DB', str(tmp_path / 'runs.sqlite'))
|
|
monkeypatch.setenv('THEHARVESTER_RUN_WORKER', 'disabled')
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('127.0.0.1', 50000)) as client:
|
|
response = client.get('/static/harvestview/app.js')
|
|
|
|
assert response.status_code == 200
|
|
assert 'function renderResults' in response.text
|
|
|
|
|
|
def test_harvestview_offers_jsonl_and_sqlite_imports_with_jsonl_export(tmp_path, monkeypatch) -> None:
|
|
from theHarvester.lib.api import api
|
|
|
|
monkeypatch.setenv('THEHARVESTER_API_KEY', 'test-key')
|
|
monkeypatch.setenv('THEHARVESTER_RUN_DB', str(tmp_path / 'runs.sqlite'))
|
|
monkeypatch.setenv('THEHARVESTER_RUN_WORKER', 'disabled')
|
|
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('127.0.0.1', 50000)) as client:
|
|
root = client.get('/')
|
|
script = client.get('/static/harvestview/app.js')
|
|
|
|
assert 'accept=".jsonl,.sqlite,.sqlite3,.db,application/x-ndjson,application/vnd.sqlite3"' in root.text
|
|
assert 'id="export-jsonl-button"' in root.text
|
|
assert 'id="route-csv-button"' not in root.text
|
|
assert 'id="export-json-button"' not in root.text
|
|
assert 'id="export-csv-button"' not in root.text
|
|
assert '/export' in script.text
|
|
assert "fileKind === 'jsonl' ? '/api/v1/runs/import' : '/api/v1/runs/import-database'" in script.text
|
|
assert '/exports/' not in script.text
|
|
assert 'text/csv' not in script.text
|
|
assert 'versioned JSONL' not in root.text
|
|
assert 'interesting-url' not in script.text
|
|
assert 'api-endpoint' not in script.text
|
|
assert 'linkedin-link' not in script.text
|
|
|
|
|
|
def test_harvestview_loads_pinned_tabulator_from_cdnjs(tmp_path, monkeypatch) -> None:
|
|
from theHarvester.lib.api import api
|
|
|
|
monkeypatch.setenv('THEHARVESTER_API_KEY', 'test-key')
|
|
monkeypatch.setenv('THEHARVESTER_RUN_DB', str(tmp_path / 'runs.sqlite'))
|
|
monkeypatch.setenv('THEHARVESTER_RUN_WORKER', 'disabled')
|
|
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('127.0.0.1', 50000)) as client:
|
|
root = client.get('/')
|
|
theme = client.get('/static/harvestview/tabulator.min.css')
|
|
script = client.get('/static/harvestview/tabulator.min.js')
|
|
license_file = client.get('/static/harvestview/TABULATOR-LICENSE')
|
|
bootstrap = client.get('/static/harvestview/bootstrap.min.css')
|
|
old_theme = client.get('/static/harvestview/tabulator_bootstrap5.min.css')
|
|
|
|
assert root.status_code == 200
|
|
assert 'bootstrap.min.css' not in root.text
|
|
assert 'tabulator_bootstrap5.min.css' not in root.text
|
|
assert (
|
|
'<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabulator-tables/6.5.2/css/tabulator.min.css" '
|
|
'integrity="sha512-t8I/asqzdu/MRgVLxVanQ/c5bhUA1qZ/zA432a/3nUh0kkd7P8Qch35wQvTODivf9D6Xv3h7F8p7ezcUyBOQrQ==" '
|
|
'crossorigin="anonymous" referrerpolicy="no-referrer">'
|
|
) in root.text
|
|
assert (
|
|
'<script src="https://cdnjs.cloudflare.com/ajax/libs/tabulator-tables/6.5.2/js/tabulator.min.js" '
|
|
'integrity="sha512-AF0YMSgc0Ui4IJPb4hJNSi16wFidZEQa6ZTCAeguF3h5glVnAPuz/JT2ai9ypKhsc9n6CEXBB+tMdxsv1q+rxg==" '
|
|
'crossorigin="anonymous" referrerpolicy="no-referrer"></script>'
|
|
) in root.text
|
|
assert 'https://unpkg.com' not in root.text
|
|
assert theme.status_code == 404
|
|
assert script.status_code == 404
|
|
assert license_file.status_code == 404
|
|
assert bootstrap.status_code == 404
|
|
assert old_theme.status_code == 404
|
|
|
|
|
|
def test_docker_mode_trusts_only_the_detected_gateway(tmp_path, monkeypatch) -> None:
|
|
from theHarvester.lib.api import api, harvestview
|
|
|
|
monkeypatch.setenv('THEHARVESTER_API_KEY', 'test-key')
|
|
monkeypatch.setenv('THEHARVESTER_RUN_DB', str(tmp_path / 'runs.sqlite'))
|
|
monkeypatch.setenv('THEHARVESTER_RUN_WORKER', 'disabled')
|
|
monkeypatch.setattr(harvestview, '_docker_gateway', lambda: ipaddress.ip_address('172.18.0.1'))
|
|
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('172.18.0.1', 50000)) as client:
|
|
disabled = client.get('/')
|
|
|
|
monkeypatch.setenv('THEHARVESTER_HARVESTVIEW_LOCAL_PROXY', 'enabled')
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('172.18.0.1', 50000)) as client:
|
|
gateway = client.get('/')
|
|
with TestClient(api.app, base_url='http://127.0.0.1', client=('172.18.0.2', 50000)) as client:
|
|
sibling = client.get('/')
|
|
with TestClient(api.app, base_url='http://attacker.example', client=('172.18.0.1', 50000)) as client:
|
|
rebound = client.get('/')
|
|
|
|
assert disabled.status_code == 403
|
|
assert gateway.status_code == 200
|
|
assert sibling.status_code == 403
|
|
assert rebound.status_code == 403
|