Skip to content

Commit 95aa0cc

Browse files
committed
refactor(config): move check_deliverability setting to defaults.ini
Signed-off-by: Amine <amine.raouane@enim.ac.ma>
1 parent a8d373b commit 95aa0cc

File tree

3 files changed

+41
-73
lines changed

3 files changed

+41
-73
lines changed

src/macaron/config/defaults.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,9 @@ cost = 1.0
612612
# The path to the file that contains the list of popular packages.
613613
popular_packages_path =
614614

615+
# A boolean value that determines whether to check the deliverability of the email address.
616+
check_deliverability = True
617+
615618
# ==== The following sections are for source code analysis using Semgrep ====
616619
# rulesets: a reference to a 'ruleset' in this section refers to a Semgrep .yaml file containing one or more rules.
617620
# rules: a reference to a 'rule' in this section refers to an individual rule ID, specified by the '- id:' field in

src/macaron/malware_analyzer/pypi_heuristics/metadata/fake_email.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from email_validator import EmailNotValidError, ValidatedEmail, validate_email
99

10+
from macaron.config.defaults import defaults
1011
from macaron.json_tools import JsonType, json_extract
1112
from macaron.malware_analyzer.pypi_heuristics.base_analyzer import BaseHeuristicAnalyzer
1213
from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult, Heuristics
@@ -24,6 +25,15 @@ def __init__(self) -> None:
2425
heuristic=Heuristics.FAKE_EMAIL,
2526
depends_on=None,
2627
)
28+
self.check_deliverability: bool = self._load_defaults()
29+
30+
def _load_defaults(self) -> bool:
31+
"""Load the default values from defaults.ini."""
32+
section_name = "heuristic.pypi"
33+
if defaults.has_section(section_name):
34+
section = defaults[section_name]
35+
return section.getboolean("check_deliverability", fallback=True)
36+
return True
2737

2838
def is_valid_email(self, email: str) -> ValidatedEmail | None:
2939
"""Check if the email format is valid and the domain has MX records.
@@ -37,15 +47,10 @@ def is_valid_email(self, email: str) -> ValidatedEmail | None:
3747
-------
3848
ValidatedEmail | None
3949
The validated email object if the email is valid, otherwise None.
40-
41-
Raises
42-
------
43-
HeuristicAnalyzerValueError
44-
if the failure is due to DNS resolution.
4550
"""
4651
emailinfo = None
4752
try:
48-
emailinfo = validate_email(email, check_deliverability=True)
53+
emailinfo = validate_email(email, check_deliverability=self.check_deliverability)
4954
except EmailNotValidError as err:
5055
err_message = f"Invalid email address: {email}. Error: {err}"
5156
logger.warning(err_message)
@@ -63,11 +68,6 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
6368
-------
6469
tuple[HeuristicResult, dict[str, JsonType]]:
6570
The result and related information collected during the analysis.
66-
67-
Raises
68-
------
69-
HeuristicAnalyzerValueError
70-
if the analysis fails.
7171
"""
7272
package_json = pypi_package_json.package_json
7373
if not package_json.get("info", {}):

tests/malware_analyzer/pypi/test_fake_email.py

Lines changed: 27 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
"""Tests for the FakeEmailAnalyzer heuristic."""
55

66

7-
from collections.abc import Generator
8-
from unittest.mock import MagicMock, patch
7+
from unittest.mock import MagicMock
98

109
import pytest
11-
from email_validator import EmailNotValidError
1210

1311
from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult
1412
from macaron.malware_analyzer.pypi_heuristics.metadata.fake_email import FakeEmailAnalyzer
@@ -22,20 +20,13 @@ def analyzer_() -> FakeEmailAnalyzer:
2220

2321

2422
@pytest.fixture(name="pypi_package_json_asset_mock")
25-
def pypi_package_json_asset_mock_fixture() -> MagicMock:
23+
def pypi_package_json_asset_mock_() -> MagicMock:
2624
"""Pytest fixture for a mock PyPIPackageJsonAsset."""
2725
mock_asset = MagicMock(spec=PyPIPackageJsonAsset)
2826
mock_asset.package_json = {}
2927
return mock_asset
3028

3129

32-
@pytest.fixture(name="mock_validate_email")
33-
def mock_validate_email_fixture() -> Generator[MagicMock]:
34-
"""Patch validate_email and mock its behavior."""
35-
with patch("macaron.malware_analyzer.pypi_heuristics.metadata.fake_email.validate_email") as mock:
36-
yield mock
37-
38-
3930
def test_analyze_skip_no_emails_present(analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock) -> None:
4031
"""Test the analyzer skips if no author_email or maintainer_email is present."""
4132
pypi_package_json_asset_mock.package_json = {"info": {"author_email": None, "maintainer_email": None}}
@@ -52,91 +43,65 @@ def test_analyze_skip_no_info_key(analyzer: FakeEmailAnalyzer, pypi_package_json
5243
assert info["message"] == "No package info available."
5344

5445

55-
def test_analyze_fail_invalid_email(
56-
analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock, mock_validate_email: MagicMock
57-
) -> None:
46+
def test_analyze_fail_invalid_email(analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock) -> None:
5847
"""Test analyzer fails for an invalid email format."""
5948
invalid_email = "invalid-email"
6049
pypi_package_json_asset_mock.package_json = {"info": {"author_email": invalid_email, "maintainer_email": None}}
61-
mock_validate_email.side_effect = EmailNotValidError("Invalid email.")
6250

6351
result, info = analyzer.analyze(pypi_package_json_asset_mock)
64-
6552
assert result == HeuristicResult.FAIL
6653
assert info == {"email": invalid_email}
67-
mock_validate_email.assert_called_once_with(invalid_email, check_deliverability=True)
6854

6955

7056
def test_analyze_pass_only_maintainer_email_valid(
71-
analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock, mock_validate_email: MagicMock
57+
analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock
7258
) -> None:
73-
"""Test analyzer passes when only maintainer_email is present and valid."""
59+
"""Test the analyzer's behavior when only a valid maintainer_email is present."""
7460
email = "maintainer@example.net"
7561
pypi_package_json_asset_mock.package_json = {"info": {"author_email": None, "maintainer_email": email}}
62+
result, info = analyzer.analyze(pypi_package_json_asset_mock)
7663

77-
mock_email_info = MagicMock()
78-
mock_email_info.normalized = "maintainer@example.net"
79-
mock_email_info.local_part = "maintainer"
80-
mock_email_info.domain = "example.net"
81-
mock_validate_email.return_value = mock_email_info
64+
if analyzer.check_deliverability:
65+
assert result == HeuristicResult.FAIL
66+
assert info == {"email": email}
67+
return
8268

83-
result, info = analyzer.analyze(pypi_package_json_asset_mock)
8469
assert result == HeuristicResult.PASS
8570
assert info["validated_emails"] == [
8671
{"normalized": "maintainer@example.net", "local_part": "maintainer", "domain": "example.net"}
8772
]
88-
mock_validate_email.assert_called_once_with(email, check_deliverability=True)
8973

9074

91-
def test_analyze_pass_both_emails_valid(
92-
analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock, mock_validate_email: MagicMock
93-
) -> None:
94-
"""Test the analyzer passes when both emails are present and valid."""
95-
96-
def side_effect(email: str, check_deliverability: bool) -> MagicMock: # pylint: disable=unused-argument
97-
local_part, domain = email.split("@")
98-
mock_email_info = MagicMock()
99-
mock_email_info.normalized = email
100-
mock_email_info.local_part = local_part
101-
mock_email_info.domain = domain
102-
return mock_email_info
103-
104-
mock_validate_email.side_effect = side_effect
75+
def test_analyze_pass_both_emails_valid(analyzer: FakeEmailAnalyzer, pypi_package_json_asset_mock: MagicMock) -> None:
76+
"""Test the analyzer's behavior when both author and maintainer emails are valid."""
77+
author_email = "example@gmail.com"
78+
author_local_part, author_domain = author_email.split("@")
79+
maintainer_email = "maintainer@example.net"
80+
maintainer_local_part, maintainer_domain = maintainer_email.split("@")
10581

10682
pypi_package_json_asset_mock.package_json = {
107-
"info": {"author_email": "author@example.com", "maintainer_email": "maintainer@example.net"}
83+
"info": {"author_email": author_email, "maintainer_email": maintainer_email}
10884
}
10985
result, info = analyzer.analyze(pypi_package_json_asset_mock)
86+
if analyzer.check_deliverability:
87+
assert result == HeuristicResult.FAIL
88+
assert info == {"email": maintainer_email}
89+
return
90+
11091
assert result == HeuristicResult.PASS
111-
assert mock_validate_email.call_count == 2
11292

11393
validated_emails = info.get("validated_emails")
11494
assert isinstance(validated_emails, list)
11595
assert len(validated_emails) == 2
116-
assert {"normalized": "author@example.com", "local_part": "author", "domain": "example.com"} in validated_emails
96+
assert {"normalized": author_email, "local_part": author_local_part, "domain": author_domain} in validated_emails
11797
assert {
118-
"normalized": "maintainer@example.net",
119-
"local_part": "maintainer",
120-
"domain": "example.net",
98+
"normalized": maintainer_email,
99+
"local_part": maintainer_local_part,
100+
"domain": maintainer_domain,
121101
} in validated_emails
122102

123103

124-
def test_is_valid_email_success(analyzer: FakeEmailAnalyzer, mock_validate_email: MagicMock) -> None:
125-
"""Test is_valid_email returns the validation object on success."""
126-
mock_validated_email = MagicMock()
127-
mock_validated_email.normalized = "test@example.com"
128-
mock_validated_email.local_part = "test"
129-
mock_validated_email.domain = "example.com"
130-
131-
mock_validate_email.return_value = mock_validated_email
132-
result = analyzer.is_valid_email("test@example.com")
133-
assert result == mock_validated_email
134-
mock_validate_email.assert_called_once_with("test@example.com", check_deliverability=True)
135-
136-
137-
def test_is_valid_email_failure(analyzer: FakeEmailAnalyzer, mock_validate_email: MagicMock) -> None:
104+
def test_is_valid_email_failure(analyzer: FakeEmailAnalyzer) -> None:
138105
"""Test is_valid_email returns None on failure."""
139-
mock_validate_email.side_effect = EmailNotValidError("The email address is not valid.")
140106
result = analyzer.is_valid_email("invalid-email")
141107
assert result is None
142-
mock_validate_email.assert_called_once_with("invalid-email", check_deliverability=True)

0 commit comments

Comments
 (0)