aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_redact.py
blob: 91ab291100e9fb727f23ce9be146d63bb1d145d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Tests for abusectl.redact: query-value redaction and redirect recovery."""

import unittest

from abusectl import redact


class TestRedactUrl(unittest.TestCase):
    def test_query_values_are_redacted_and_names_kept(self):
        # The names fingerprint the kit; the values identify the recipient.
        self.assertEqual(
            redact.url("http://login.example.invalid/verify?id=abc&src=mail"),
            "http://login.example.invalid/verify?id=REDACTED&src=REDACTED",
        )

    def test_a_url_with_no_query_is_unchanged(self):
        self.assertEqual(
            redact.url("http://login.example.invalid/verify"),
            "http://login.example.invalid/verify",
        )

    def test_scheme_host_and_path_survive(self):
        self.assertEqual(
            redact.url("https://a.example.invalid/one/two/three?x=1"),
            "https://a.example.invalid/one/two/three?x=REDACTED",
        )

    def test_a_valueless_parameter_keeps_its_shape(self):
        self.assertEqual(
            redact.url("http://a.example.invalid/p?flag"),
            "http://a.example.invalid/p?flag=REDACTED",
        )

    def test_repeated_parameter_names_are_all_redacted(self):
        self.assertEqual(
            redact.url("http://a.example.invalid/p?t=1&t=2"),
            "http://a.example.invalid/p?t=REDACTED&t=REDACTED",
        )


class TestSuspectPathSegments(unittest.TestCase):
    def test_a_base64_looking_segment_is_flagged(self):
        # Flagged for review, NOT redacted: a path may be meaningful.
        found = redact.suspect_path_segments(
            "http://a.example.invalid/verify/dGVzdEBleGFtcGxlLm9yZw/"
        )
        self.assertEqual(found, ["dGVzdEBleGFtcGxlLm9yZw"])

    def test_a_long_hex_segment_is_flagged(self):
        found = redact.suspect_path_segments(
            "http://a.example.invalid/c/5f4dcc3b5aa765d61d8327deb882cf99"
        )
        self.assertEqual(found, ["5f4dcc3b5aa765d61d8327deb882cf99"])

    def test_ordinary_path_words_are_not_flagged(self):
        found = redact.suspect_path_segments(
            "http://a.example.invalid/account/verify/now"
        )
        self.assertEqual(found, [])

    def test_a_short_segment_is_not_flagged(self):
        # "news" is base64-shaped and four characters. Too short to carry an
        # address, and flagging it would train the user to ignore the flag.
        found = redact.suspect_path_segments("http://a.example.invalid/news")
        self.assertEqual(found, [])


class TestUrlValuedParameters(unittest.TestCase):
    def test_a_redirect_target_is_recovered(self):
        found = redact.url_valued_parameters(
            "http://t.example.invalid/c?url=http%3A%2F%2Fevil.example.invalid%2Fp"
        )
        self.assertEqual(found, ["http://evil.example.invalid/p"])

    def test_a_tracking_token_is_not_mistaken_for_one(self):
        found = redact.url_valued_parameters(
            "http://t.example.invalid/c?u=dGVzdEBleGFtcGxlLm9yZw"
        )
        self.assertEqual(found, [])

    def test_the_original_is_still_fully_redacted(self):
        # Recovery does not loosen the rule: the redirector itself keeps every
        # value blanked, including the one the target was recovered from.
        raw = "http://t.example.invalid/c?url=http%3A%2F%2Fe.example.invalid%2Fp&u=tok"
        self.assertEqual(
            redact.url(raw),
            "http://t.example.invalid/c?url=REDACTED&u=REDACTED",
        )


if __name__ == "__main__":
    unittest.main()