diff --git a/bson/objectid.py b/bson/objectid.py index 4422c98955..c5591aaac4 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -112,6 +112,10 @@ def __init__(self, oid: Optional[Union[str, ObjectId, bytes]] = None) -> None: self.__id = bytes.fromhex(oid) except (TypeError, ValueError): _raise_invalid_id(oid) + # bytes.fromhex() skips ASCII whitespace, so a 24-character + # string can decode to fewer than 12 bytes. + if len(self.__id) != 12: + _raise_invalid_id(oid) else: _raise_invalid_id(oid) elif isinstance(oid, ObjectId): diff --git a/doc/changelog.rst b/doc/changelog.rst index 02bfd1e46e..8b2d94becd 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,9 +17,15 @@ Bug fixes ``ValueError``, even though ``maxPoolSize=0`` means "no limit". The ``minPoolSize <= maxPoolSize`` validation now treats both ``0`` and ``None`` as unlimited (`PYTHON-6149`_). +- Fixed a bug where :class:`~bson.objectid.ObjectId` accepted a 24-character + string containing whitespace and created an ObjectId shorter than 12 bytes + from it. It now raises :class:`~bson.errors.InvalidId`, and + :meth:`~bson.objectid.ObjectId.is_valid` returns ``False`` for such a string + (`PYTHON-5873`_). .. _PYTHON-6172: https://jira.mongodb.org/browse/PYTHON-6172 .. _PYTHON-6149: https://jira.mongodb.org/browse/PYTHON-6149 +.. _PYTHON-5873: https://jira.mongodb.org/browse/PYTHON-5873 Changes in Version 4.19.0 (2026/10/14) -------------------------------------- diff --git a/test/test_objectid.py b/test/test_objectid.py index 516ae6b9f9..561f6dcb81 100644 --- a/test/test_objectid.py +++ b/test/test_objectid.py @@ -57,6 +57,16 @@ def test_from_hex(self): ObjectId("123456789012123456789012") self.assertRaises(InvalidId, ObjectId, "123456789012123456789G12") + def test_from_hex_whitespace(self): + # bytes.fromhex() skips whitespace, which must not shorten the ObjectId. + self.assertRaises(InvalidId, ObjectId, "0123 0011") + self.assertRaises(InvalidId, ObjectId, "00 " * 8) + self.assertRaises(InvalidId, ObjectId, "01\n" * 8) + self.assertRaises(InvalidId, ObjectId, " " * 24) + # The string form of an ObjectId round-trips. + oid = ObjectId() + self.assertEqual(oid, ObjectId(str(oid))) + def test_repr_str(self): self.assertEqual( repr(ObjectId("1234567890abcdef12345678")), "ObjectId('1234567890abcdef12345678')" @@ -154,6 +164,8 @@ def test_is_valid(self): self.assertFalse(ObjectId.is_valid("")) self.assertFalse(ObjectId.is_valid("12345678901")) self.assertFalse(ObjectId.is_valid("1234567890123")) + self.assertFalse(ObjectId.is_valid("0123 0011")) + self.assertFalse(ObjectId.is_valid(" " * 24)) self.assertTrue(ObjectId.is_valid(b"123456789012")) self.assertTrue(ObjectId.is_valid("123456789012123456789012"))