refactor build_reputation to use ReputationContext dataclass

This commit is contained in:
Andrew Pimenov 2026-08-09 16:08:29 +03:00
parent fc37ff369d
commit 4c290ec309
3 changed files with 52 additions and 39 deletions

View file

@ -208,6 +208,7 @@ async def _bgp_section(client, settings: Settings, asn: str | None, raw: dict):
async def _reputation_section(client, settings: Settings, options: Options, geo: IpGeo, raw: dict): async def _reputation_section(client, settings: Settings, options: Options, geo: IpGeo, raw: dict):
from netsleuth.reputation import ( from netsleuth.reputation import (
build_reputation, build_reputation,
ReputationContext,
fetch_abuseipdb, fetch_abuseipdb,
fetch_internetdb, fetch_internetdb,
normalize_internetdb, normalize_internetdb,
@ -235,6 +236,7 @@ async def _reputation_section(client, settings: Settings, options: Options, geo:
else: else:
warnings.append("ABUSEIPDB_API_KEY not set — abuse score skipped") warnings.append("ABUSEIPDB_API_KEY not set — abuse score skipped")
reputation = build_reputation( reputation = build_reputation(
ReputationContext(
internetdb=normalize_internetdb(payload), internetdb=normalize_internetdb(payload),
firehol_hits=index.hits(geo.ip), firehol_hits=index.hits(geo.ip),
dnsbl_outcomes=outcomes, dnsbl_outcomes=outcomes,
@ -242,6 +244,7 @@ async def _reputation_section(client, settings: Settings, options: Options, geo:
abuseipdb_score=score, abuseipdb_score=score,
abuseipdb_reports=reports, abuseipdb_reports=reports,
) )
)
return ModuleResult( return ModuleResult(
name="reputation", status="partial" if warnings else "ok", data=reputation, warnings=warnings name="reputation", status="partial" if warnings else "ok", data=reputation, warnings=warnings
) )

View file

@ -208,26 +208,29 @@ def _captcha_risk_full(
return risk, "; ".join(reasons), "; ".join(reasons_ru) return risk, "; ".join(reasons), "; ".join(reasons_ru)
def build_reputation( @dataclass
internetdb: InternetDbResult | None, class ReputationContext:
firehol_hits: list[str], ip_type: str
dnsbl_outcomes: list[DnsblOutcome] | None, internetdb: InternetDbResult | None
ip_type: str, firehol_hits: list[str]
abuseipdb_score: int | None, dnsbl_outcomes: list[DnsblOutcome] | None
abuseipdb_reports: int | None, abuseipdb_score: int | None
) -> Reputation: abuseipdb_reports: int | None
def build_reputation(ctx: ReputationContext) -> Reputation:
hits: list[DnsblHit] | None = None hits: list[DnsblHit] | None = None
blocked = False blocked = False
if dnsbl_outcomes is not None: if ctx.dnsbl_outcomes is not None:
hits, blocked = summarize_dnsbl(dnsbl_outcomes) hits, blocked = summarize_dnsbl(ctx.dnsbl_outcomes)
risk, rationale, rationale_ru = _captcha_risk_full(firehol_hits, hits or [], ip_type, abuseipdb_score) risk, rationale, rationale_ru = _captcha_risk_full(ctx.firehol_hits, hits or [], ctx.ip_type, ctx.abuseipdb_score)
return Reputation( return Reputation(
internetdb=internetdb, internetdb=ctx.internetdb,
firehol_hits=firehol_hits, firehol_hits=ctx.firehol_hits,
dnsbl_hits=hits, dnsbl_hits=hits,
dnsbl_query_blocked=blocked, dnsbl_query_blocked=blocked,
abuseipdb_score=abuseipdb_score, abuseipdb_score=ctx.abuseipdb_score,
abuseipdb_reports=abuseipdb_reports, abuseipdb_reports=ctx.abuseipdb_reports,
captcha_risk=risk, captcha_risk=risk,
rationale=rationale, rationale=rationale,
rationale_ru=rationale_ru, rationale_ru=rationale_ru,

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from netsleuth.reputation import ( from netsleuth.reputation import (
NetsetIndex, NetsetIndex,
build_reputation, build_reputation,
ReputationContext,
captcha_risk, captcha_risk,
decode_dnsbl, decode_dnsbl,
normalize_internetdb, normalize_internetdb,
@ -132,6 +133,7 @@ def test_captcha_risk_escalates_on_a_high_abuseipdb_score():
def test_build_reputation_marks_the_query_as_blocked_without_inventing_hits(api_fixture): def test_build_reputation_marks_the_query_as_blocked_without_inventing_hits(api_fixture):
rep = build_reputation( rep = build_reputation(
ReputationContext(
internetdb=normalize_internetdb(api_fixture("internetdb.json")), internetdb=normalize_internetdb(api_fixture("internetdb.json")),
firehol_hits=[], firehol_hits=[],
dnsbl_outcomes=[decode_dnsbl("zen.spamhaus.org", ["127.255.255.254"])], dnsbl_outcomes=[decode_dnsbl("zen.spamhaus.org", ["127.255.255.254"])],
@ -139,6 +141,7 @@ def test_build_reputation_marks_the_query_as_blocked_without_inventing_hits(api_
abuseipdb_score=None, abuseipdb_score=None,
abuseipdb_reports=None, abuseipdb_reports=None,
) )
)
assert rep.dnsbl_hits == [] assert rep.dnsbl_hits == []
assert rep.dnsbl_query_blocked is True assert rep.dnsbl_query_blocked is True
assert rep.captcha_risk == "low" assert rep.captcha_risk == "low"
@ -146,6 +149,7 @@ def test_build_reputation_marks_the_query_as_blocked_without_inventing_hits(api_
def test_build_reputation_without_dnsbl_leaves_the_field_none(api_fixture): def test_build_reputation_without_dnsbl_leaves_the_field_none(api_fixture):
rep = build_reputation( rep = build_reputation(
ReputationContext(
internetdb=normalize_internetdb(api_fixture("internetdb.json")), internetdb=normalize_internetdb(api_fixture("internetdb.json")),
firehol_hits=["firehol_level1"], firehol_hits=["firehol_level1"],
dnsbl_outcomes=None, dnsbl_outcomes=None,
@ -153,6 +157,7 @@ def test_build_reputation_without_dnsbl_leaves_the_field_none(api_fixture):
abuseipdb_score=None, abuseipdb_score=None,
abuseipdb_reports=None, abuseipdb_reports=None,
) )
)
assert rep.dnsbl_hits is None assert rep.dnsbl_hits is None
assert rep.dnsbl_query_blocked is False assert rep.dnsbl_query_blocked is False
assert rep.captcha_risk == "high" assert rep.captcha_risk == "high"
@ -181,6 +186,7 @@ def test_build_reputation_rationale_ru_is_populated_and_cyrillic_for_every_reaso
] ]
for case in cases: for case in cases:
rep = build_reputation( rep = build_reputation(
ReputationContext(
internetdb=normalize_internetdb(api_fixture("internetdb.json")), internetdb=normalize_internetdb(api_fixture("internetdb.json")),
firehol_hits=case["firehol_hits"], firehol_hits=case["firehol_hits"],
dnsbl_outcomes=case["dnsbl_outcomes"], dnsbl_outcomes=case["dnsbl_outcomes"],
@ -188,6 +194,7 @@ def test_build_reputation_rationale_ru_is_populated_and_cyrillic_for_every_reaso
abuseipdb_score=case["abuseipdb_score"], abuseipdb_score=case["abuseipdb_score"],
abuseipdb_reports=None, abuseipdb_reports=None,
) )
)
assert rep.rationale assert rep.rationale
assert rep.rationale_ru assert rep.rationale_ru
assert has_cyrillic(rep.rationale_ru) assert has_cyrillic(rep.rationale_ru)