diff --git a/src/urlscan/pro/__init__.py b/src/urlscan/pro/__init__.py index 3ac11de..eeef013 100644 --- a/src/urlscan/pro/__init__.py +++ b/src/urlscan/pro/__init__.py @@ -2,10 +2,12 @@ from functools import cached_property from typing import BinaryIO +from urllib.parse import quote_plus from urlscan.client import BaseClient from urlscan.iterator import SearchIterator from urlscan.pro.visibility import Visibility +from urlscan.types import MaliciousObservableType from urlscan.utils import _compact from .brand import Brand @@ -276,3 +278,20 @@ def get_user(self) -> dict: """ return self.get_json("/api/v1/pro/username") + + def lookup_malicious_observable( + self, + type_: MaliciousObservableType, + value: str, + ): + """Look up how often an observable has been seen in malicious scan results and when it was first and last seen. + + Returns: + dict: Malicious observable lookup result. + + Reference: + https://docs.urlscan.io/apis/urlscan-openapi/malicious/maliciouslookup + + """ + path = f"/api/v1/malicious/{type_}/{quote_plus(value)}" + return self.get_json(path) diff --git a/src/urlscan/types.py b/src/urlscan/types.py index dba64ef..04b6006 100644 --- a/src/urlscan/types.py +++ b/src/urlscan/types.py @@ -35,3 +35,4 @@ WatchedAttributeType = Literal[ "detections", "tls", "dns", "labels", "page", "meta", "ip" ] +MaliciousObservableType = Literal["url", "domain", "ip", "hostname"] diff --git a/tests/integration/pro/test_pro.py b/tests/integration/pro/test_pro.py new file mode 100644 index 0000000..e50635b --- /dev/null +++ b/tests/integration/pro/test_pro.py @@ -0,0 +1,13 @@ +import pytest + +from urlscan import Pro + + +@pytest.mark.integration +def test_lookup_malicious_observable(pro: Pro): + type_ = "url" + value = "https://example.com" + result = pro.lookup_malicious_observable(type_="url", value=value) + assert isinstance(result, dict) + assert result["type"] == type_ + assert result["observable"] in value diff --git a/tests/unit/pro/test_pro.py b/tests/unit/pro/test_pro.py index 81216be..7bf2eea 100644 --- a/tests/unit/pro/test_pro.py +++ b/tests/unit/pro/test_pro.py @@ -35,3 +35,23 @@ def test_get_user(pro: Pro, httpserver: HTTPServer): got = pro.get_user() assert got == data + + +def test_lookup_malicious_observable(pro: Pro, httpserver: HTTPServer): + type_ = "url" + value = "https://example.com" + data = { + "type": type_, + "value": value, + "count": 5, + "lastSeen": "2024-06-01T12:00:00.000Z", + "firstSeen": "2024-01-01T08:00:00.000Z", + } + httpserver.expect_request( + # pytest-httpserver (werkzeug) normalizes URL path so no need to quote_plus it in the test + f"/api/v1/malicious/{type_}/{value}", + method="GET", + ).respond_with_json(data) + + got = pro.lookup_malicious_observable(type_, value) # type: ignore[arg-type] + assert got == data