Skip to content

feat: add observer participant role for signature requests - #8143

Open
lfals wants to merge 100 commits into
LibreSign:mainfrom
lfals:feat/participant-role-observer
Open

lfals wants to merge 100 commits into
LibreSign:mainfrom
lfals:feat/participant-role-observer

Conversation

@lfals

@lfals lfals commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Resolves: #6957

📝 Summary

Adds an observer participant role (UI label: Observer / Testemunha) to signature requests. Observers can follow document progress and open a read-only view of the request (including PDF and signature positions), but cannot sign or perform write/signing operations. The feature is gated by the policy enable_observer_profile (default: disabled).

Backend is the source of truth: role validation, signing denial, status/progress calculations, DocMDP signer limits, sequential signing, e-mail/notification routing, UUID authorization, and API contracts all treat observers separately from signers. The request UI exposes separate Signers and Observers lists when the policy is enabled.

Authorization is split explicitly:

  • RequireSignRequestUuid — signer-only (signing UI, id-docs, signature elements, and other write paths reject observers)
  • RequireParticipantUuid — participant read-only (PDF/getPdfFile allows both signers and observers)

Also includes: policy snapshots for existing requests/envelopes, preserve observers when reordering signers, require at least one signer before requesting signatures, hide signing-order numbers for observers, OBSERVING status 4 in API summaries, observer mail copy (“ready to view”), Account observer deep-links to the authenticated read-only filelist, and focused regression coverage (PHPUnit, Vitest, Behat, Playwright).

🧪 How to test

  1. Enable the policy Enable observer profile (system, group, or user scope as needed).
  2. Open the Files app, select a PDF, and open Request signature.
  3. Use Add → Signer and Add → Observer to add participants (Add menu appears only when the policy is enabled; otherwise keep the classic Add signer button).
  4. Confirm the UI shows two sections (Signers / Observers).
  5. Try requesting signatures with only observers → must be blocked (toast + API error).
  6. Add at least one signer and request signatures → request should succeed; observers must not appear as pending signers or receive signing-order numbers.
  7. Confirm observers are hidden from the signature-positions placement targets; “View signature positions” stays available for read-only review when there is at least one signer.
  8. As an Account observer, open the notification/link → authenticated read-only request sidebar (filelist/sign?uuid=…), not the public validation/sign flow.
  9. As an Email observer, open the mail link → public validation / view document; subject/body use “ready to view”.
  10. In ordered numeric flow, reorder signers with observers present → observers remain in the list without order counters.
  11. Open validation for a document with both roles → signers and observers appear in separate groups; observer status shows Observing when applicable.
  12. Authorization boundary: with an anonymous request using an observer UUID header:
    • GET /apps/libresign/pdf/<OBSERVER_UUID>200
    • POST /api/v1/id-docs and POST /api/v1/signature/elements422 (“Observers cannot sign this document”)
  13. Confirm visible signature elements cannot be assigned to observers (UI + API).

🎨 UI / Front‑end changes

  • Policy-gated Add → Signer / Observer menu (classic Add signer when policy disabled)
  • Separate Signers and Observers lists in request and validation views
  • Observer-specific labels in participant modal (search, name validation, custom message)
  • Client-side guard: block signature request when only observers are present
  • Drag-and-drop reorder in ordered flow preserves observers; no signing-order enumerator for observers
  • Hide observers from signature-positions placement; allow read-only open of positions when signers exist
  • Authenticated observers open a read-only request sidebar; Files list canSign ignores observers
  • Public validation CSS on UUID pages; anonymous View document opens the public PDF directly
  • “Send notification” action for observers (uses validation/update mail templates)
  • Screenshots before/after
image Captura de Tela 2026-09-04 às 16 12 59 image
🏚️ Before 🏡 After
Single signer list only Signers + Observers sections when policy enabled
  • Tested in multiple browsers (Chrome, Firefox, Safari) – optional but appreciated
  • Components, Unit (with vitest) and/or e2e (with Playwright) tests added - Required
  • Accessibility verified (contrast, keyboard navigation, screen reader friendly) – if applicable
  • Design review approved – optional, link to feedback if available
  • Documentation updated (if applicable) – docs repository

🚧 Tasks

  • Sync translations via Transifex before release (strings live in source until the bot updates l10n/)
  • Add/update user-facing documentation for observer role in LibreSign documentation

⚙️ API / Back‑end changes

  • participant_role column on sign requests (signer | observer) with migration
  • Policy provider enable_observer_profile (default false, system/group/user scopes) with per-request/envelope policy snapshot
  • Validation: reject observers when policy disabled; reject signing and visible-element assignment for observers
  • Exclude observers from signing status, envelope progress, sequential signing, and DocMDP signer counts
  • Observer notifications: Email → validation / “ready to view”; Account → authenticated read-only filelist deep-link
  • Require at least one signing participant when moving out of draft / requesting signatures
  • Map OBSERVING as status 4 in signer summaries; reject invalid ParticipantRole values; freeze role after signed
  • UUID auth split: RequireParticipantUuid (read-only PDF) vs RequireSignRequestUuid (signer-only writes)
  • Unit and/or integration tests added – required for backend changes
  • Capabilities updated (if applicable) – if adding/modifying Nextcloud capabilities
  • Documentation updated (if applicable) - docs repository
  • API documentation updated with the command composer openapi if necessary

🚧 Tasks

  • Confirm no environment still depends on experimental participant_profile_id (column was never shipped in official migrations)

✅ Checklist

  • I have read and followed the contribution guide.
  • OpenAPI specs and TypeScript types regenerated (composer openapi, npm run typescript:generate)
  • Focused PHPUnit, Vitest, Behat, and Playwright coverage for observer flows
  • Conventional Commits with DCO sign-off on all commits

🤖 AI (if applicable)

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

lfals added 10 commits August 30, 2026 20:46
Introduce signer/observer participant roles at the database layer so
request-signature flows can distinguish signing participants from
view-only observers.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Allow administrators to enable observer participants through the policy
workbench before requesters can assign view-only roles.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Persist participant roles, block observers from signing, and keep
sequential signing logic scoped to actual signers only.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Replace the single add-signer action with an add dropdown, separate
signers from observers in the participant list, and hide multi-signer
controls when only one signer is present.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Run occ as www-data when setup runs as root and add a reusable script
to reconfigure LibreSign after environment restarts.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Remove the experimental column that was not mapped by SignRequest and
broke participant creation in local development databases.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Observers must not affect partial or fully signed file status, envelope
progress, or pending signer checks used for signing order.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Send observer emails and in-app links to the validation page instead of
the signing flow, and redirect legacy sign URLs to validation.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Show the plus icon and Add label on the participant dropdown action.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Group signers and observers in validation UI, recognize OBSERVING status,
and ignore observers when checking partial or full signature completion.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
lfals added 8 commits August 31, 2026 09:35
Register observer UI strings in en_GB and translate the role as
testemunha in pt_BR and pt_PT while keeping Observer in English.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Force-refresh validation data when opening the page right after signing
so the first load does not fail before the signed document is ready.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Register Add and Add participant strings used by the request signature
tab so the dropdown button is localized in English and Portuguese.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Switch SignerSelect copy and NcSelect input label based on participant
role so adding a witness shows observer-specific search text.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Block observer-only signature requests in the UI and API, showing an
error toast instead of treating the operation as successful.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Preserve observers when reordering signers, exclude them from DocMDP
signer limits, map OBSERVING status in summaries, regenerate OpenAPI
types, and add focused regression tests for policy and signing rules.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Keep lint-php-cs CI green after observer workflow changes.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
This reverts commit bd2f828.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
@lfals
lfals force-pushed the feat/participant-role-observer branch from 130da68 to cfa42bc Compare August 31, 2026 12:35
@codecov-commenter

codecov-commenter commented Aug 31, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 60.60000% with 197 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rc/components/RightSidebar/RequestSignatureTab.vue 44.80% 83 Missing and 18 partials ⚠️
src/components/Request/IdentifySigner.vue 48.71% 10 Missing and 10 partials ⚠️
...kbench/settings/observer-profile/realDefinition.ts 45.71% 16 Missing and 3 partials ⚠️
src/components/validation/EnvelopeValidation.vue 77.77% 8 Missing and 2 partials ⚠️
src/views/Validation.vue 66.66% 7 Missing and 3 partials ⚠️
src/components/Signers/Signers.vue 70.00% 5 Missing and 4 partials ⚠️
...ngs/observer-profile/ObserverProfileRuleEditor.vue 66.66% 6 Missing and 1 partial ⚠️
src/components/Request/VisibleElements.vue 57.14% 4 Missing and 2 partials ⚠️
src/components/Request/SignerSelect.vue 66.66% 1 Missing and 2 partials ⚠️
src/components/validation/SignerDetails.vue 57.14% 3 Missing ⚠️
... and 5 more
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Files with missing lines Coverage Δ
src/components/RightSidebar/RightSidebar.vue 82.35% <100.00%> (+0.53%) ⬆️
src/components/validation/SigningProgress.vue 76.28% <100.00%> (-0.19%) ⬇️
src/constants.js 100.00% <ø> (ø)
src/services/validationDocument.ts 75.60% <ø> (-0.10%) ⬇️
src/store/files.js 75.97% <100.00%> (+1.11%) ⬆️
src/utils/filesListSidebar.ts 82.75% <100.00%> (ø)
src/utils/viewer.js 91.66% <100.00%> (+0.75%) ⬆️
...bench/settings/observerValidationAccessConflict.ts 100.00% <100.00%> (ø)
...ttings/PolicyWorkbench/settings/realDefinitions.ts 100.00% <ø> (ø)
...s/validation-access/ValidationAccessRuleEditor.vue 75.00% <100.00%> (ø)
... and 16 more

... and 204 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lfals
lfals marked this pull request as draft August 31, 2026 13:45
lfals added 5 commits August 31, 2026 11:45
Add a shared helper for the renamed Add participant control and update
E2E specs that previously clicked the removed Add signer button.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Centralize account/email signer picking on the combobox used by the
add-participant dialog so specs stop relying on removed placeholders.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Update request-signature E2E specs to select account and email signers
through the shared combobox helpers.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
When an Everyone rule already exists, edit it instead of waiting for a
missing Everyone scope option, and make save paths dirty the form first.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
…tate

Reset stale certificates/signatures when needed, keep signature stamp in
a graphic-capable mode, and click Sign document with a viewport-safe helper.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
@lfals
lfals marked this pull request as ready for review August 31, 2026 17:33
@lfals
lfals marked this pull request as draft August 31, 2026 17:33
Comment thread lib/ResponseDefinitions.php Outdated
Comment thread lib/Service/MailService.php Outdated
Comment thread lib/Listener/NotificationListener.php
…links

Address PR review feedback for OBSERVING status in the contract, observer email wording, and authenticated deep-links for Account observers.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
@vitormattos vitormattos added this to the Next Major (36) milestone Sep 13, 2026
Keep observer SignerModel fields and add visibleElements from main.
@lfals
lfals requested a review from vitormattos September 13, 2026 19:13
Comment thread lib/Controller/LibresignTrait.php Outdated
@vitormattos

Copy link
Copy Markdown
Member

Sorry for the many back-and-forth review rounds on this PR.

The change touches many parts of LibreSign, so it ended up being a more difficult review than expected. Thank you for your patience and for fixing the issues as we found them.

I think we are close now. Before the final review, there are two points left:

  • update the PR description to match the current implementation;
  • address my latest inline comment about the observer authorization flow.

After that, I will review the PR again as a whole.

@lfals

lfals commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for the many back-and-forth review rounds on this PR.

The change touches many parts of LibreSign, so it ended up being a more difficult review than expected. Thank you for your patience and for fixing the issues as we found them.

I think we are close now. Before the final review, there are two points left:

  • update the PR description to match the current implementation;
  • address my latest inline comment about the observer authorization flow.

After that, I will review the PR again as a whole.

Don't worry, it's the right thing to do. It's a long PR too

Keep RequireSignRequestUuid signer-only so observer UUIDs cannot hit
write endpoints, and authorize PDF viewing via RequireParticipantUuid.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Avoid PHPUnit coverage risky failures when the trait stub also executes
entity and page-aware controller code under beStrictAboutCoverageMetadata.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Comment on lines +41 to +42
// TRANSLATORS Toggle description explaining that document owners can add view-only participants.
const description = t('libresign', 'When enabled, document owners can add observers who track progress without signing.')

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.

There is one policy interaction that is still not clear to the administrator.

An Email observer uses the public validation page, but when make_validation_url_private is enabled that page requires authentication. An external observer without a Nextcloud account will receive the email but will not be able to open the document.

I think both policies can still be enabled together, but we should warn about this consequence.

Could we show a warning when enabling Observer while validation access is authenticated-only, and also when making validation private while Observer is enabled?

Please also add regression coverage for this combination, including the Email observer flow.

Show a workbench warning when both policies are enabled, and cover the
email observer flow that cannot open authenticated-only validation.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Expose sibling effective flags via resolvedStateMeta so the Workbench
warning is backed by policy resolution instead of frontend-only store reads.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Deduplicate the repeated observer/signer subject, heading, and action
cascades in notifySignDataUpdated and notifyUnsignedUser.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Unify RequireSignRequestUuid and RequireParticipantUuid handling through
authorizeRequiredUuid, and rename loadNextcloudFileFromSignRequestUuid to
the role-neutral loadNextcloudFileFromUuid.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Centralize policy_snapshot effectiveValue reads in
ObserverProfilePolicyValue so the service and file applier stay in sync.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Add groupCount, userCount, and everyoneCount so Policy Workbench editor
specs type-check against the OpenAPI EffectivePolicyState shape.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
PolicySourceTest still constructed ValidationAccessPolicy with no args
after the sibling-meta reader dependency was added.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Comment thread lib/Service/Policy/Provider/Helper/SiblingPolicyEffectiveBoolReader.php Outdated
Use the active resolve context when reading sibling effective booleans so
group/user workbench warnings do not follow the admin personal posture.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Nextcloud cannot autowire IPolicySource, which broke policy resolution
across PHPUnit, Behat, and Playwright. Also apply php-cs on the new test.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
The only remaining failure was list.feature with cURL error 52 while
195 other scenarios passed on the same run.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
SQLite Behat CI keeps failing randomly with cURL error 52 empty reply
from the PHP built-in server while other DB matrices stay green.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
@lfals
lfals requested a review from vitormattos September 14, 2026 01:30
Comment on lines +43 to +65
/**
* Retry transient PHP built-in server disconnects that show up in CI as
* cURL error 52 (Empty reply from server) during long Behat runs.
*
* @param TableNode|\Behat\Gherkin\Node\PyStringNode|array|null $body
*/
public function sendRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void {
$attempts = 0;
$maxAttempts = 3;
while (true) {
try {
parent::sendRequest($verb, $url, $body, $headers, $options);
return;
} catch (\GuzzleHttp\Exception\ConnectException $exception) {
$attempts++;
if ($attempts >= $maxAttempts) {
throw $exception;
}
usleep(250000 * $attempts);
}
}
}

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.

This global retry looks unrelated to the Observer feature.

It was added after the SQLite Behat run failed with cURL error 52 / Empty reply from server, but this error only tells us that the test server did not return a response. It does not explain why the server stopped replying.

Overriding sendRequest() changes every Behat scenario and can retry POST, PATCH and DELETE requests too. If a write was already processed before the connection was lost, the retry can execute it twice. It can also hide a real PHP server or application failure.

Could we revert this change from this PR and investigate the SQLite/PHP built-in server failure separately?

If this is a general CI problem, it should have its own fix and regression coverage instead of changing the request behavior for the whole Behat suite.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vitormattos Can i open a issue for the Behat problem?

return true;
}
if ($snapshotValue === false) {
return $this->isLivePolicyEnabled();

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.

Could we add a scoped-policy regression test for this fallback before changing the implementation?

For an existing request with enable_observer_profile = false in its snapshot, the code can check the live policy when an observer is added later.

I want to confirm that this uses the policy context of the target request, and not only the policy effective for the user doing the update.

A useful test would have different values for the manager/admin and for the target user or group.

Please cover this in PHPUnit and, if possible, also in Behat with group/user policy scopes.

We should test both directions:

  • manager policy disabled, target policy enabled;
  • manager policy enabled, target policy disabled.

If both cases already follow the target context, no fix is needed. If not, please keep the tests as regression coverage and use the correct target context when checking and upgrading the snapshot.

The retry was added for a SQLite empty-reply flake and changed request
behavior for the whole suite; investigate that CI failure separately.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
When a disabled observer snapshot is upgraded or re-checked, use the
file owner's policy context instead of the acting manager/current user.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Nextcloud master removed OCP\IServerContainer; InitialStateService now
takes Psr\Container\ContainerInterface.

Signed-off-by: Luis Amorim <luisfelipeamorim@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 1. to do

Development

Successfully merging this pull request may close these issues.

Add participant role permissions: Sign and Observe

3 participants