Skip to content

feat: add backend support for signature rejection - #8257

Open
Sufiyan-MSA wants to merge 1 commit into
LibreSign:mainfrom
Sufiyan-MSA:feat/signature-rejection-backend
Open

feat: add backend support for signature rejection#8257
Sufiyan-MSA wants to merge 1 commit into
LibreSign:mainfrom
Sufiyan-MSA:feat/signature-rejection-backend

Conversation

@Sufiyan-MSA

Copy link
Copy Markdown

Resolves : #8159

Summary

Adds the backend foundation for the signature rejection workflow described in #7832: a signer
can decline to sign, the refusal is persisted with its timestamp and optional comment, and the
rules governing it come from a new signature_rejection policy in the existing Policies & Rules
architecture. No temporary global app configuration was introduced.

Disabled by default — with the default policy value, behavior is identical to today.

How to test

  1. As admin, enable the policy:
    POST /ocs/v2.php/apps/libresign/api/v1/policies/system/signature_rejection
    with value = {"enabled":true,"comment_mode":"required","cancel_workflow":true}
  2. Create a signature request with two signers.
  3. As the first signer, POST /ocs/v2.php/apps/libresign/api/v1/sign/file_id/<FILE_ID>/reject
    with no comment → 422, "A comment is required to reject this signature request."
  4. Retry with comment=I do not agree → 200, status: 3, workflowCanceled: true.
  5. As the second signer, try to sign → refused; the file is now status 6 (Canceled).
  6. Repeat with cancel_workflow=false and confirm the second signer can still sign.
  7. Check the requester's Activity stream for the rejection entry.

Automated coverage: tests/integration/features/sign/reject.feature.

API / Back-end changes

  • New signature_rejection policy provider (system + group scope, group-admin delegation).
    Sub-rules: comment_mode (disabled/optional/required), allow_private_comment,
    cancel_workflow, public_status, show_comment_on_validation. A delegated rule cannot
    downgrade a comment the parent marked as required.
  • Rules are frozen per document through a FilePolicy applier writing to
    metadata.policy_snapshot, so a later policy change never alters an in-flight workflow.
  • SignRequestStatus::REJECTED and FileStatus::CANCELED; migration
    Version18004Date20260906000000 adds rejected_at, rejection_comment and
    rejection_comment_private to libresign_sign_request.
  • SignatureRejectionService enforces the rules, persists the rejection, closes the workflow
    when configured, and dispatches SignatureRejectedEvent.
  • Endpoints POST /api/v1/sign/file_id/{fileId}/reject and
    POST /api/v1/sign/uuid/{uuid}/reject.
  • Rejection data is exposed on the signer payload through
    SignatureRejectionVisibilityService: the requester and the rejecting signer always see the
    full record; other readers see only what the policy makes public, and a comment marked
    private is never disclosed to them.
  • Actions after closure are blocked by the existing fileCanBeSigned() status gate; a second
    rejection and rejection after signing are refused explicitly.
  • hasPendingLowerOrderSigners() now treats a rejected signer as no longer pending, so
    ordered-numeric flows can continue past a rejection when the policy allows it.
  • Rejections are registered in the audit trail via a new Activity provider and setting.

Comment on lines +25 to +33
#[\Override]
public function apply(FileEntity $file, array $data): void {
$user = ($data['userManager'] ?? null) instanceof IUser ? $data['userManager'] : null;
$activeContext = $this->extractActiveContext($data);
$resolvedPolicy = $activeContext === null
? $this->policyService->resolveForUser(SignatureRejectionPolicy::KEY, $user)
: $this->policyService->resolveForUser(SignatureRejectionPolicy::KEY, $user, [], $activeContext);
$this->storeSignatureRejectionPolicySnapshot($file, $resolvedPolicy);
}

@vitormattos vitormattos Sep 5, 2026

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.

I think the rejection policy should control whether the requester is allowed to enable rejection for a signature request.

When the policy is enabled, the requester should be able to choose if rejection is enabled for that file or envelope. This choice must be stored with the request and become the effective value for the signing flow.

When the policy is disabled, the requester must not be able to enable rejection, including through a direct API request.

The requester should still be able to change this choice while the signing flow has not started. Once the flow starts, the stored choice must stay frozen. A later policy change must also not change the behavior of an existing request.

Could we follow the same request override and snapshot lifecycle used in #8143, and add integration coverage for normal files and envelopes?

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.

Thanks, this is much closer, but there are still two cases to fix.

First, if the policy has enabled: true and the request does not send a rejection choice, the current code enables rejection for that request automatically.

This is not the behavior we want. The policy should only allow the requester to enable rejection. If the requester does not choose it, rejection should stay disabled for that request.

Second, if a draft already has rejection disabled and later the requester edits something else without sending the rejection setting again, the current code can enable rejection because it resolves the policy again.

Please keep the stored request value when no new rejection value is sent.

Before the signing flow starts, the requester should still be able to change the rejection setting explicitly.

Please add integration tests for these cases, including an envelope:

  • policy enabled + no request choice → rejection stays disabled;
  • draft with rejection disabled + unrelated update → rejection stays disabled;
  • requester can explicitly change the setting before the signing flow starts;
  • after the signing flow starts, the stored value cannot change.

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 code now looks correct for the cases we discussed. Thanks.

I think we only need to complete the integration coverage for envelopes before resolving this thread.

We already test that an enabled policy alone does not enable rejection for an envelope, but the request lifecycle tests below only cover a normal file.

Could we add an envelope scenario that confirms the stored rejection choice is preserved after the request is created, including after a later policy change or an unrelated draft update?

This is important because file and envelope requests can follow different backend paths.

Comment thread lib/Service/SignatureRejection/SignatureRejectionService.php
Comment thread tests/integration/features/sign/reject.feature Outdated
@github-project-automation github-project-automation Bot moved this from 0. Backlog to 1. to do in Roadmap Sep 5, 2026
@Sufiyan-MSA
Sufiyan-MSA force-pushed the feat/signature-rejection-backend branch 2 times, most recently from 435954a to 12fe7d3 Compare September 6, 2026 10:59
Signed-off-by: Mohammed Sufiyan Ahmed <mohammed.sufiyan.msa@gmail.com>
@Sufiyan-MSA
Sufiyan-MSA force-pushed the feat/signature-rejection-backend branch from 12fe7d3 to a277f15 Compare September 6, 2026 11:57
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.

Backend support for signature rejection

2 participants