Phase 5 Iteration 1: Bảo mật — mã hoá + quyền hạn, gỡ mật khẩu, redaction THẬT, xoá metadata
Engine: - qpdf.rs: Permissions (in/sửa/copy/chú thích) map sang cờ AES-256 của qpdf; encrypt_with_password_perms (API cũ giữ tương thích); decrypt_remove_password (--password --decrypt, sai mật khẩu trả lỗi không ghi file rác) - redact.rs MỚI — redaction thật, nghiêng về xoá thừa (an toàn): text/path/shading/form giao vùng → xoá nguyên object; ảnh giao vùng → đọc pixel gốc qua get_raw_image, BÔI ĐEN đúng phần giao trong dữ liệu ảnh rồi thay ảnh (không đọc được → xoá cả ảnh); vẽ khối đen phủ vùng - meta.rs: strip_metadata — xoá /Info trailer + XMP /Metadata catalog, xoá cả tham chiếu lẫn object đích (lopdf; file trailer phi chuẩn qua qpdf trước) Tauri + UI (thanh 🔒 Bảo mật): - Commands: redact_apply (nhiều trang, chain file tạm), security_encrypt, security_decrypt, security_strip_metadata - Redact 2 bước như Foxit: tool quét vùng (khối đen mờ viền đỏ, bấm để bỏ, giữ tool quét nhiều vùng) → Áp dụng (n) → lưu file mới - Dialog Đặt mật khẩu (user/confirm/owner + 4 checkbox quyền), Gỡ mật khẩu, Xoá metadata — modal chuyên nghiệp, không prompt/alert Test (6 mới, tổng 58/58 ngoài qpdf-fixture): - redact: chuỗi mật biến mất khỏi extract + tâm vùng render đen; đọc lại RAW image từ file kết quả — nửa redact đen trong chính dữ liệu ảnh - encrypt+perms round-trip + qpdf --show-encryption xác nhận cấm in; decrypt gỡ mật khẩu, mật khẩu sai fail; metadata biến mất khỏi BYTES file - qpdf_safety: 5/7 xanh trên Linux; 2 test repair fixture corrupt-truncated fail do khác biệt qpdf 11.9 Linux (bản Windows dự án xử lý được) — ghi chú trong docs/15, cần xác nhận lại trên máy Windows Docs: 15-phase5-summary (kèm kế hoạch Iteration 2: chữ ký số PAdES, lưu tối ưu, redact tỉa ký tự), 16-phase5-user-tests (S1–S12), README, roadmap, 14. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
This commit is contained in:
@@ -94,28 +94,82 @@ pub fn repair(input: &Path, output: &Path) -> Result<(), EngineError> {
|
||||
run_qpdf(&[&i, &o])
|
||||
}
|
||||
|
||||
/// Mã hoá `input` (đang KHÔNG mã hoá) thành `output`, dùng AES-256, với user
|
||||
/// password (mở file) và owner password (đổi quyền) chỉ định. Permissions để
|
||||
/// mặc định (đầy đủ) cho V1 — đủ cho DoD "ghi không làm hỏng file mã hoá".
|
||||
/// Quyền hạn áp khi mã hoá (Phase 5) — map thẳng sang cờ AES-256 của qpdf.
|
||||
/// Mặc định: cho phép tất cả (chỉ chặn bằng mật khẩu mở file).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Permissions {
|
||||
/// In tài liệu (`--print=full|none`).
|
||||
pub allow_print: bool,
|
||||
/// Sửa nội dung (`--modify=all|none`).
|
||||
pub allow_modify: bool,
|
||||
/// Sao chép/trích text & ảnh (`--extract=y|n`).
|
||||
pub allow_extract: bool,
|
||||
/// Thêm/sửa chú thích + điền form (`--annotate=y|n`).
|
||||
pub allow_annotate: bool,
|
||||
}
|
||||
|
||||
impl Default for Permissions {
|
||||
fn default() -> Self {
|
||||
Self { allow_print: true, allow_modify: true, allow_extract: true, allow_annotate: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// Mã hoá `input` (đang KHÔNG mã hoá) thành `output`, AES-256, permissions
|
||||
/// đầy đủ. Giữ cho tương thích — bản đầy đủ: [`encrypt_with_password_perms`].
|
||||
pub fn encrypt_with_password(
|
||||
input: &Path,
|
||||
output: &Path,
|
||||
user_password: &str,
|
||||
owner_password: &str,
|
||||
) -> Result<(), EngineError> {
|
||||
encrypt_with_password_perms(input, output, user_password, owner_password, Permissions::default())
|
||||
}
|
||||
|
||||
/// Mã hoá AES-256 với user password (mở file), owner password (đổi quyền) và
|
||||
/// bộ quyền hạn chỉ định. Owner password rỗng → qpdf dùng user password làm
|
||||
/// owner (tránh file "khoá quyền nhưng ai cũng mở được" thiếu chủ đích).
|
||||
pub fn encrypt_with_password_perms(
|
||||
input: &Path,
|
||||
output: &Path,
|
||||
user_password: &str,
|
||||
owner_password: &str,
|
||||
perms: Permissions,
|
||||
) -> Result<(), EngineError> {
|
||||
let i = path_arg(input);
|
||||
let o = path_arg(output);
|
||||
let owner = if owner_password.is_empty() { user_password } else { owner_password };
|
||||
let print = if perms.allow_print { "--print=full" } else { "--print=none" };
|
||||
let modify = if perms.allow_modify { "--modify=all" } else { "--modify=none" };
|
||||
let extract = if perms.allow_extract { "--extract=y" } else { "--extract=n" };
|
||||
let annotate = if perms.allow_annotate { "--annotate=y" } else { "--annotate=n" };
|
||||
run_qpdf(&[
|
||||
"--encrypt",
|
||||
user_password,
|
||||
owner_password,
|
||||
owner,
|
||||
"256",
|
||||
print,
|
||||
modify,
|
||||
extract,
|
||||
annotate,
|
||||
"--",
|
||||
&i,
|
||||
&o,
|
||||
])
|
||||
}
|
||||
|
||||
/// GỠ mật khẩu/mã hoá: giải mã `input` (mở bằng `password`) và ghi bản KHÔNG
|
||||
/// mã hoá ra `output`. Cần đúng password (user hoặc owner).
|
||||
pub fn decrypt_remove_password(
|
||||
input: &Path,
|
||||
password: &str,
|
||||
output: &Path,
|
||||
) -> Result<(), EngineError> {
|
||||
let i = path_arg(input);
|
||||
let o = path_arg(output);
|
||||
let pw = format!("--password={password}");
|
||||
run_qpdf(&[&pw, "--decrypt", &i, &o])
|
||||
}
|
||||
|
||||
/// Mở `input` bằng PDFium an toàn cho cả file hỏng và file mã hoá:
|
||||
/// 1. Nếu mở thẳng được (đúng password nếu có) → dùng luôn.
|
||||
/// 2. Nếu PDFium báo lỗi format (không phải do password) → thử `repair()` rồi mở lại.
|
||||
|
||||
Reference in New Issue
Block a user