jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1358207?usp=email )

Change subject: [bugfix] Use uciprange for IP ranges in usercontribs
......................................................................

[bugfix] Use uciprange for IP ranges in usercontribs

Site.usercontribs() and User.contributions() sent an IP range in CIDR
notation as ucuser, which the API rejects with a baduser error. Send it
as uciprange instead. The API also accepts a range with host bits set,
so add a strict parameter to tools.is_ip_network() to detect it.

Bug: T362377
Change-Id: I1f27935ea0cf361e28d2f53fb1780ecd26ee6e32
---
M pywikibot/site/_generators.py
M pywikibot/tools/__init__.py
M tests/tools_tests.py
M tests/user_tests.py
4 files changed, 76 insertions(+), 4 deletions(-)

Approvals:
  jenkins-bot: Verified
  Xqt: Looks good to me, approved




diff --git a/pywikibot/site/_generators.py b/pywikibot/site/_generators.py
index 2dea689..43b0f49 100644
--- a/pywikibot/site/_generators.py
+++ b/pywikibot/site/_generators.py
@@ -33,6 +33,7 @@
     deprecated_args,
     deprecated_signature,
     is_ip_address,
+    is_ip_network,
 )
 from pywikibot.tools.itertools import filter_unique, union_generators

@@ -1728,7 +1729,11 @@
            All parameters are keyword-only now. The *new*, *patrolled*,
            and *autopatrolled* parameters were added.

-        :param user: Iterate contributions by this user (name or IP).
+        .. version-changed:: 11.10
+           An IP range in CIDR notation is accepted as *user*.
+
+        :param user: Iterate contributions by this user (name, IP or IP
+            range in CIDR notation).
         :param userprefix: Iterate contributions by all users whose
             names or IPs start with this substring.
         :param start: Iterate contributions starting at this Timestamp.
@@ -1784,7 +1789,11 @@
             formatversion=formatversion
         )
         if user:
-            ucgen.request['ucuser'] = user
+            # the API expects an IP range in uciprange, not in ucuser
+            if '/' in user and is_ip_network(user, strict=False):
+                ucgen.request['uciprange'] = user
+            else:
+                ucgen.request['ucuser'] = user
         if userprefix:
             ucgen.request['ucuserprefix'] = userprefix
         if reverse:
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index d49481a..dcf271c 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -117,16 +117,21 @@
     return False


-def is_ip_network(value: str) -> bool:
+def is_ip_network(value: str, *, strict: bool = True) -> bool:
     """Check if a value is a valid range of IPv4 or IPv6 addresses.
 
     .. version-added:: 9.0
+    .. version-changed:: 11.10
+       The *strict* parameter was added.
     .. seealso:: :func:`is_ip_address`

     :param value: Value to check
+    :param strict: If ``True`` (default), the host bits of the address
+        must be zero; if ``False``, they are ignored, so
+        ``'1.2.3.4/24'`` is accepted
     """
     with suppress(ValueError):
-        ipaddress.ip_network(value)
+        ipaddress.ip_network(value, strict=strict)
         return True

     return False
diff --git a/tests/tools_tests.py b/tests/tools_tests.py
index 4d2db4e..32f719c 100755
--- a/tests/tools_tests.py
+++ b/tests/tools_tests.py
@@ -1077,6 +1077,29 @@
                 self.assertFalse(is_ip_network(ip_range))


+class TestIsIpNetworkNotStrict(TestCase):
+
+    """Unit test class for is_ip_network with the strict parameter."""
+
+    net = False
+
+    def test_host_bits(self) -> None:
+        """Check that host bits are only accepted with strict=False."""
+        for ip_range in ('1.2.3.4/24', '5.119.174.237/16', '192.168.0.0/8',
+                         '2001:0db8::/16'):
+            with self.subTest(ip_network=ip_range):
+                self.assertFalse(is_ip_network(ip_range))
+                self.assertFalse(is_ip_network(ip_range, strict=True))
+                self.assertTrue(is_ip_network(ip_range, strict=False))
+
+    def test_invalid_ranges(self) -> None:
+        """Check that invalid values are rejected with strict=False."""
+        for ip_range in (None, '', '/32', 'a.b.c.d', '1.2.3.256/24',
+                         '2001:db8::aaaa::1/128'):
+            with self.subTest(ip_network=ip_range):
+                self.assertFalse(is_ip_network(ip_range, strict=False))
+
+
 class TestHasModule(TestCase):

     """Unit test class for has_module."""
diff --git a/tests/user_tests.py b/tests/user_tests.py
index 11f52a7..0d496bd 100755
--- a/tests/user_tests.py
+++ b/tests/user_tests.py
@@ -355,6 +355,41 @@
             self.assertEqual(event.user(), user.username)


+class TestUsercontribsUserParameter(TestCase):
+
+    """Test how usercontribs() passes the user to the API."""
+
+    family = 'wikipedia'
+    code = 'test'
+    dry = True
+
+    def get_request(self, user: str) -> dict:
+        """Return the request parameters set by usercontribs()."""
+        with patch.object(self.site, '_generator') as generator, \
+                patch('pywikibot.data.api.OptionSet'):
+            generator.return_value.request = {}
+            self.site.usercontribs(user=user)
+        return generator.return_value.request
+
+    def test_ip_range(self) -> None:
+        """Test that an IP range is passed as uciprange."""
+        for user in ('5.119.0.0/16', '5.119.174.237/16', '1.2.3.4/32',
+                     '2001:db8::/32'):
+            with self.subTest(user=user):
+                request = self.get_request(user)
+                self.assertEqual(request['uciprange'], user)
+                self.assertNotIn('ucuser', request)
+
+    def test_user_and_ip(self) -> None:
+        """Test that a name or a single IP is passed as ucuser."""
+        for user in ('Example', 'Example/Subpage', '5.119.174.237',
+                     '2001:db8::1'):
+            with self.subTest(user=user):
+                request = self.get_request(user)
+                self.assertEqual(request['ucuser'], user)
+                self.assertNotIn('uciprange', request)
+
+
 class TestUserRefreshParameter(DeprecationTestCase):

     """Test the rename of the *force* parameter to *refresh*."""

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1358207?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.wikimedia.org/r/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I1f27935ea0cf361e28d2f53fb1780ecd26ee6e32
Gerrit-Change-Number: 1358207
Gerrit-PatchSet: 2
Gerrit-Owner: AkashKumar7902 <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: Mahveotm <[email protected]>
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to