mirror of
https://github.com/laramies/theHarvester.git
synced 2026-09-24 02:24:55 +02:00
Adds a new passive source that calls the Dymo data verifier endpoint (https://api.tpeoficial.com/v1/private/secure/verify) with the target domain. The response is stored on the source for downstream inspection and any matching canonical domain or didYouMean suggestion is added to the host set so the rest of the pipeline can resolve it. The source follows the existing plugin shape: SearchDymo lives in theHarvester/discovery/dymosearch.py, the API key is read from the ``dymo`` block in api-keys.yaml via Core.dymo_key(), and the engine is registered in get_supportedengines() and the -b/--source dispatcher. Tests cover the missing-key path, header/payload shape, the empty, unrelated-suggestion and non-dict-response branches, and the get_supportedengines() registration. Closes #2300.
146 lines
5.4 KiB
Python
146 lines
5.4 KiB
Python
import pytest
|
|
|
|
from theHarvester.discovery import dymosearch
|
|
from theHarvester.discovery.constants import MissingKey
|
|
|
|
|
|
def _patch_dymo_key(monkeypatch, value):
|
|
import theHarvester.lib.core as core_module
|
|
|
|
monkeypatch.setattr(core_module.Core, 'dymo_key', staticmethod(lambda: value), raising=True)
|
|
monkeypatch.setattr(core_module.Core, 'get_user_agent', staticmethod(lambda: 'UA'), raising=True)
|
|
|
|
|
|
class TestDymoSearch:
|
|
def test_missing_key_raises(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, None)
|
|
with pytest.raises(MissingKey):
|
|
dymosearch.SearchDymo('example.com')
|
|
|
|
def test_init_sets_state(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token-123')
|
|
search = dymosearch.SearchDymo('example.com')
|
|
assert search.word == 'example.com'
|
|
assert search.key == 'token-123'
|
|
assert search.proxy is False
|
|
assert search.totalhosts == set()
|
|
assert search.results == {}
|
|
|
|
def test_headers_use_bearer(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token-abc')
|
|
search = dymosearch.SearchDymo('example.com')
|
|
headers = search._headers()
|
|
assert headers['Authorization'] == 'Bearer token-abc'
|
|
assert headers['Content-Type'] == 'application/json'
|
|
assert headers['User-Agent'] == 'UA'
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_extracts_canonical_and_suggestion(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token-xyz')
|
|
captured = {}
|
|
|
|
async def fake_post_fetch(url, headers=None, data='', params='', json=False, proxy=False):
|
|
captured['url'] = url
|
|
captured['headers'] = headers
|
|
captured['data'] = data
|
|
captured['proxy'] = proxy
|
|
return {
|
|
'domain': {
|
|
'valid': True,
|
|
'fraud': False,
|
|
'freeSubdomain': False,
|
|
'domain': 'exemple.com',
|
|
'didYouMean': 'www.exemple.com',
|
|
},
|
|
'url': {
|
|
'valid': True,
|
|
'domain': 'exemple.com',
|
|
'didYouMean': None,
|
|
},
|
|
}
|
|
|
|
import theHarvester.lib.core as core_module
|
|
|
|
monkeypatch.setattr(core_module.AsyncFetcher, 'post_fetch', classmethod(lambda cls, *a, **kw: fake_post_fetch(*a, **kw)))
|
|
|
|
search = dymosearch.SearchDymo('exemple.com')
|
|
await search.process(proxy=True)
|
|
|
|
assert captured['url'] == dymosearch.SearchDymo.VERIFY_URL
|
|
assert captured['proxy'] is True
|
|
assert captured['data'] == {'domain': 'exemple.com', 'url': 'https://exemple.com'}
|
|
assert captured['headers']['Authorization'] == 'Bearer token-xyz'
|
|
|
|
hosts = await search.get_hostnames()
|
|
results = await search.get_results()
|
|
|
|
assert 'exemple.com' in hosts
|
|
assert 'www.exemple.com' in hosts
|
|
assert results['domain']['valid'] is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_handles_empty_payload(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token')
|
|
|
|
async def fake_post_fetch(url, headers=None, data='', params='', json=False, proxy=False):
|
|
return {}
|
|
|
|
import theHarvester.lib.core as core_module
|
|
|
|
monkeypatch.setattr(core_module.AsyncFetcher, 'post_fetch', classmethod(lambda cls, *a, **kw: fake_post_fetch(*a, **kw)))
|
|
|
|
search = dymosearch.SearchDymo('example.com')
|
|
await search.process()
|
|
assert await search.get_hostnames() == set()
|
|
assert await search.get_results() == {}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_ignores_unrelated_suggestion(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token')
|
|
|
|
async def fake_post_fetch(url, headers=None, data='', params='', json=False, proxy=False):
|
|
return {
|
|
'domain': {
|
|
'valid': True,
|
|
'domain': 'totally-different.org',
|
|
'didYouMean': 'somewhere-else.net',
|
|
},
|
|
}
|
|
|
|
import theHarvester.lib.core as core_module
|
|
|
|
monkeypatch.setattr(core_module.AsyncFetcher, 'post_fetch', classmethod(lambda cls, *a, **kw: fake_post_fetch(*a, **kw)))
|
|
|
|
search = dymosearch.SearchDymo('example.com')
|
|
await search.process()
|
|
# Neither contains 'example.com', so neither should be added.
|
|
assert await search.get_hostnames() == set()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_handles_non_dict_response(self, monkeypatch):
|
|
_patch_dymo_key(monkeypatch, 'token')
|
|
|
|
async def fake_post_fetch(url, headers=None, data='', params='', json=False, proxy=False):
|
|
return '<html>error</html>'
|
|
|
|
import theHarvester.lib.core as core_module
|
|
|
|
monkeypatch.setattr(core_module.AsyncFetcher, 'post_fetch', classmethod(lambda cls, *a, **kw: fake_post_fetch(*a, **kw)))
|
|
|
|
search = dymosearch.SearchDymo('example.com')
|
|
await search.process()
|
|
assert await search.get_hostnames() == set()
|
|
assert search.results == {}
|
|
|
|
|
|
class TestDymoIntegration:
|
|
def test_module_exposes_class(self, monkeypatch):
|
|
from theHarvester.discovery import dymosearch as mod
|
|
|
|
assert hasattr(mod, 'SearchDymo')
|
|
|
|
def test_supportedengines_lists_dymo(self):
|
|
from theHarvester.lib.core import Core
|
|
|
|
assert 'dymo' in Core.get_supportedengines()
|