diff --git a/pypdf/_reader.py b/pypdf/_reader.py index c4157c2bb..de26084d1 100644 --- a/pypdf/_reader.py +++ b/pypdf/_reader.py @@ -1808,8 +1808,26 @@ def decrypt(self, password: Union[str, bytes]) -> PasswordType: # TODO: raise Exception for wrong password return self._encryption.verify(password) - def decode_permissions(self, permissions_code: int) -> Dict[str, bool]: - # Takes the permissions as an integer, returns the allowed access + def decode_permissions(self, permissions_code: Optional[int]) -> Dict[str, bool]: + """ + Decodes the permissions as a dictionary + + Args: + permissions_code: integer providing bits from an integer + if None is provided, the value is retrieved from the default pdf fields + Individual permissions using PKCS#7 shall be extracted manually + + Returns: + permissions as human readable structure in a dict + Can be used to feed permissions_flag within PdfWriter.encrypt + """ + if permissions_code is None: + permissions_code = cast( + int, + cast(DictionaryObject, self.trailer[TK.ENCRYPT]).get("/P", -1) + if TK.ENCRYPT in self.trailer + else -1, + ) permissions = {} permissions["print"] = permissions_code & (1 << 3 - 1) != 0 # bit 3 permissions["modify"] = permissions_code & (1 << 4 - 1) != 0 # bit 4 diff --git a/pypdf/_writer.py b/pypdf/_writer.py index e12c2e8ab..05d2297b7 100644 --- a/pypdf/_writer.py +++ b/pypdf/_writer.py @@ -122,7 +122,7 @@ ) OPTIONAL_READ_WRITE_FIELD = FieldFlag(0) -ALL_DOCUMENT_PERMISSIONS = UserAccessPermissions((2**31 - 1) - 3) +ALL_DOCUMENT_PERMISSIONS = UserAccessPermissions((2**32 - 1) - 3) class ObjectDeletionFlag(enum.IntFlag): @@ -1065,7 +1065,9 @@ def encrypt( user_password: str, owner_password: Optional[str] = None, use_128bit: bool = True, - permissions_flag: UserAccessPermissions = ALL_DOCUMENT_PERMISSIONS, + permissions_flag: Union[ + UserAccessPermissions, Dict[str, bool] + ] = ALL_DOCUMENT_PERMISSIONS, *, algorithm: Optional[str] = None, ) -> None: @@ -1088,6 +1090,8 @@ def encrypt( Bit position 3 is for printing, 4 is for modifying content, 5 and 6 control annotations, 9 for form fields, 10 for extraction of text and graphics. + permissions can be provided as an integer or as a dictionary + as provided by `PdfReader.decode_permissions()` algorithm: encrypt algorithm. Values maybe one of "RC4-40", "RC4-128", "AES-128", "AES-256-R5", "AES-256". If it's valid, `use_128bit` will be ignored. @@ -1095,6 +1099,26 @@ def encrypt( if owner_password is None: owner_password = user_password + if isinstance(permissions_flag, dict): + permissions = permissions_flag + _flag = cast(int, ALL_DOCUMENT_PERMISSIONS) + if not permissions.get("print", False): + _flag -= 1 << 3 + if not permissions.get("modify", False): + _flag -= 1 << 4 + if not permissions.get("copy", False): + _flag -= 1 << 5 + if not permissions.get("annotations", False): + _flag -= 1 << 6 + if not permissions.get("forms", False): + _flag -= 1 << 9 + if not permissions.get("accessability", False): + _flag -= 1 << 10 + if not permissions.get("assemble", False): + _flag -= 1 << 11 + if not permissions.get("print_high_quality", False): + _flag -= 1 << 12 + permissions_flag = cast(UserAccessPermissions, _flag) if algorithm is not None: try: alg = getattr(EncryptAlgorithm, algorithm.replace("-", "_")) diff --git a/sample-files b/sample-files index 8c405ece5..964fb47b0 160000 --- a/sample-files +++ b/sample-files @@ -1 +1 @@ -Subproject commit 8c405ece5eff12396a34a1fae3276132002e1753 +Subproject commit 964fb47b0fbfa89864680582640ae4eedf143890