diff --git a/pypdf/_writer.py b/pypdf/_writer.py index 00b9d498c..b516d4fd2 100644 --- a/pypdf/_writer.py +++ b/pypdf/_writer.py @@ -1209,6 +1209,8 @@ def encrypt( "AES-128", "AES-256-R5", "AES-256". If it is valid, `use_128bit` will be ignored. """ + permissions_flag.check() + if owner_password is None: owner_password = user_password diff --git a/pypdf/constants.py b/pypdf/constants.py index 745774e2a..76aa84124 100644 --- a/pypdf/constants.py +++ b/pypdf/constants.py @@ -133,8 +133,26 @@ def from_dict(cls, value: Dict[str, bool]) -> "UserAccessPermissions": @classmethod def all(cls) -> "UserAccessPermissions": + """Get full permissions value.""" return cls((2**32 - 1) - cls.R1 - cls.R2) + @classmethod + def minimal(cls) -> "UserAccessPermissions": + """Get the minimal permissions value.""" + result = cls(0) + for name, flag in cls.__members__.items(): + if cls._is_reserved(name) and cls._is_active(name): + result |= flag + return result + + def check(self) -> None: + """Check if the flags are valid.""" + for name, flag in UserAccessPermissions.__members__.items(): + if self._is_reserved(name): + expected = flag if self._is_active(name) else 0 + if self & flag != expected: + raise ValueError(f"Invalid value for reserved bit {name}.") + class Resources: """ diff --git a/tests/test_constants.py b/tests/test_constants.py index d53ebed33..6410805aa 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -114,3 +114,31 @@ def test_user_access_permissions__all(): assert all_int & UserAccessPermissions.PRINT == UserAccessPermissions.PRINT assert all_int & UserAccessPermissions.R7 == UserAccessPermissions.R7 assert all_int & UserAccessPermissions.R31 == UserAccessPermissions.R31 + + +def test_user_access_permissions__minimal(): + minimal_permissions = UserAccessPermissions.minimal() + + assert int(minimal_permissions) == 4294963392 # All reserved bits which should be one. + + assert minimal_permissions & UserAccessPermissions.R1 == 0 + assert minimal_permissions & UserAccessPermissions.R2 == 0 + assert minimal_permissions & UserAccessPermissions.PRINT == 0 + assert minimal_permissions & UserAccessPermissions.R7 == UserAccessPermissions.R7 + assert minimal_permissions & UserAccessPermissions.R31 == UserAccessPermissions.R31 + + +def test_user_access_permissions__check(): + all_permissions = UserAccessPermissions.all() + all_permissions.check() + + r1_set = all_permissions | UserAccessPermissions.R1 + with pytest.raises(ValueError, match="Invalid value for reserved bit R1."): + r1_set.check() + r2_set = all_permissions | UserAccessPermissions.R2 + with pytest.raises(ValueError, match="Invalid value for reserved bit R2."): + r2_set.check() + + r8_unset = UserAccessPermissions(all_permissions - UserAccessPermissions.R8) + with pytest.raises(ValueError, match="Invalid value for reserved bit R8."): + r8_unset.check() diff --git a/tests/test_reader.py b/tests/test_reader.py index 0a2a32b81..6d5dc4f0f 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -749,12 +749,12 @@ def test_user_access_permissions(): writer.encrypt( user_password="", owner_password="abc", - permissions_flag=UAP.PRINT | UAP.FILL_FORM_FIELDS, + permissions_flag=UAP.minimal() | UAP.PRINT | UAP.FILL_FORM_FIELDS, ) output = BytesIO() writer.write(output) reader = PdfReader(output) - assert reader.user_access_permissions == (UAP.PRINT | UAP.FILL_FORM_FIELDS) + assert reader.user_access_permissions == (UAP.minimal() | UAP.PRINT | UAP.FILL_FORM_FIELDS) # All writer permissions. writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf") diff --git a/tests/test_writer.py b/tests/test_writer.py index 9dfeffdd8..3ef25526b 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -20,6 +20,7 @@ Transformation, ) from pypdf.annotations import Link +from pypdf.constants import UserAccessPermissions as UAP from pypdf.errors import PageSizeNotDefinedError, PyPdfError from pypdf.generic import ( ArrayObject, @@ -576,6 +577,42 @@ def test_encrypt(use_128bit, user_password, owner_password, pdf_file_path): assert new_text == orig_text +def test_user_access_permissions(): + # All writer permissions. + writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf") + writer.encrypt( + user_password="", + owner_password="abc", + permissions_flag=UAP.all(), + ) + output = BytesIO() + writer.write(output) + reader = PdfReader(output) + assert reader.user_access_permissions == UAP.all() + + # Minimal permissions. + writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf") + writer.encrypt( + user_password="", + owner_password="abc", + permissions_flag=UAP.minimal(), + ) + output = BytesIO() + writer.write(output) + reader = PdfReader(output) + assert reader.user_access_permissions == UAP.minimal() + + # Wrong permissions. + writer = PdfWriter(clone_from=RESOURCE_ROOT / "crazyones.pdf") + with pytest.raises(ValueError, match="Invalid value for reserved bit R2."): + writer.encrypt( + user_password="", + owner_password="abc", + permissions_flag=UAP.minimal() | UAP.R2, + ) + + + def test_add_outline_item(pdf_file_path): reader = PdfReader(RESOURCE_ROOT / "pdflatex-outline.pdf") writer = PdfWriter()