Skip to content

fix: store identification documents of external signers under the file owner - #8368

Merged
vitormattos merged 2 commits into
LibreSign:mainfrom
maia-andre:fix/8364-id-docs-owner-folder
Sep 12, 2026
Merged

fix: store identification documents of external signers under the file owner#8368
vitormattos merged 2 commits into
LibreSign:mainfrom
maia-andre:fix/8364-id-docs-owner-folder

Conversation

@maia-andre

Copy link
Copy Markdown
Contributor

Resolves: #8364

📝 Summary

When an unauthenticated (external) signer uploads an identification document, IdDocsService::addFilesToDocumentFolder() called RequestSignatureService::saveFile() without a userManager. FileService::getNodeFromData() only sets the FolderService user id from that key, so the node was written to appdata_<id>/libresign/unauthenticated/. In the same call, saveFile() sets File.user_id to the owner of the signed file — and AccountService::getPdfByUuid() later uses that user_id to look the node up in the owner's folder, where it never was. Result: intact file on disk, empty viewer and 404 on download in the validation UI.

The fix resolves the owner of the signed file with the same lookup saveFile() already uses for user_id (SignRequest::getFileId()FileMapper::getById()getUserId()IUserManager::get()) and passes it as userManager, mirroring addIdDocs(). If the owner cannot be resolved (no file id, missing row, deleted account) nothing is passed and the current behaviour is kept.

IUserManager is appended at the end of the constructor so the change applies to the stable branches, whose constructor differs from main after #8336.

🧪 How to test

Unit tests in IdDocsServiceTest:

  • testAddFilesToDocumentFolderStoresFilesUnderTheOwnerOfTheSignedFile — fails on main with Failed asserting that null is identical to an object of class IUser, passes with the fix;
  • testAddFilesToDocumentFolderWithoutResolvableOwnerKeepsCurrentBehaviouruserManager is not set when the owner cannot be resolved.

Local checks in the devcontainer: IdDocsServiceTest OK (7 tests, 18 assertions); psalm on IdDocsService unchanged (2 pre-existing MissingDependency); php-cs-fixer clean.

Manual, as in the issue: enable Require identification documents, request a signature from an email recipient without an account, upload the document as that signer, then as an approval_group admin open it in Documents Validation — the viewer shows the pages and download works. The node now lives in the owner's LibreSign folder.

⚙️ API / Back‑end changes

  • IdDocsService gains an IUserManager dependency (container-resolved); no API change
  • Unit tests added

🚧 Backport

Bug present on stable32stable35; the cherry-pick applies cleanly to all four (checked locally on each branch).

Not covered on purpose

  • Documents already written to the appdata folder on affected installs stay unreachable through the UI; a repair command is a separate change (offered in the issue).
  • The file name shown without extension is a different symptom.

✅ Checklist

🤖 AI (if applicable)

  • The content of this PR was partially or fully generated using AI

…e owner

When an unauthenticated signer uploads an identification document,
IdDocsService::addFilesToDocumentFolder() called saveFile() without a
userManager. FolderService then had no user id, so the node was written
to the unauthenticated appdata folder, while the File row received the
owner of the signed file as user_id — the value getPdfByUuid() later
uses to look the node up. The document was intact on disk but unreachable
from the validation UI (empty viewer, 404 on download).

Resolve the owner of the signed file with the same lookup saveFile()
already uses for user_id and pass it as userManager, mirroring
addIdDocs(). Uploads for a sign request whose owner cannot be resolved
keep the current behaviour.

Resolves: LibreSign#8364

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: André Maia <andrefnkmm@gmail.com>

@vitormattos vitormattos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks correct overall. I am requesting one change before merge: please narrow the catch (\Throwable) to the expected exception from FileMapper::getById(), so unexpected failures are not silently converted into the unauthenticated storage fallback.

As a non-blocking follow-up, this bug may be worth an architecture issue. The storage decision needs the correct user context before the node is created, while saveFile() can also infer the owner later from the SignRequest when setting File.user_id. This allowed storage and metadata to disagree.

The amount of setup needed in the new unit tests may be another sign that this responsibility is spread across too many collaborators. It may be worth reviewing this flow separately to see if it can be made harder to misuse and simpler to test.

Comment thread lib/Service/IdDocsService.php Outdated
@github-project-automation github-project-automation Bot moved this from 0. Backlog to 1. to do in Roadmap Sep 12, 2026
…cs storage

getOwnerOfSignedFile() caught \Throwable, which turned unexpected
database or programming errors into the unauthenticated appdata fallback
— the same inconsistent state this change is fixing. Catch only the
DoesNotExistException documented by FileMapper::getById() and let other
failures surface.

Add one test for the missing row (falls back, no userManager) and one
asserting that an unexpected exception is propagated before saveFile()
is reached.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: André Maia <andrefnkmm@gmail.com>
@maia-andre

Copy link
Copy Markdown
Contributor Author

Done in 9dd0790: getOwnerOfSignedFile() now catches only DoesNotExistException, the exception FileMapper::getById() documents. Two tests were added: a missing file row keeps the current fallback (no userManager, saveFile() still called), and an unexpected exception from the mapper is propagated before saveFile() is reached — the second one fails against the previous \Throwable catch. I used the fully-qualified class name in the catch (as CrlService does) so the hunk does not sit between use lines that differ from the stable branches; the cherry-pick of both commits still applies cleanly to stable32stable35.

On the follow-up: agreed that the storage decision and saveFile()'s later owner inference are two places deciding the same thing. If you want, I can open the architecture issue with the two code paths and the test setup as evidence.

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable35

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable34

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable33

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 4. to release

2 participants