Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.22% covered (success)
97.22%
35 / 36
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SignatureFlowFilePolicyApplier
97.22% covered (success)
97.22%
35 / 36
83.33% covered (warning)
83.33%
5 / 6
14
0.00% covered (danger)
0.00%
0 / 1
 apply
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 sync
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 supportsCoreFlowSync
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOverrides
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 assertOverrideAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 assertFrozenFlowIsKept
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service\Policy\Provider\Signature\FilePolicy;
10
11use OCA\Libresign\Db\File as FileEntity;
12use OCA\Libresign\Enum\SignatureFlow;
13use OCA\Libresign\Exception\LibresignException;
14use OCA\Libresign\Service\Policy\AbstractFilePolicyApplier;
15use OCA\Libresign\Service\Policy\Model\ResolvedPolicy;
16use OCA\Libresign\Service\Policy\Provider\Signature\SignatureFlowPolicy;
17use OCP\AppFramework\Http;
18use OCP\IUser;
19
20class SignatureFlowFilePolicyApplier extends AbstractFilePolicyApplier {
21
22    #[\Override]
23    public function apply(FileEntity $file, array $data): void {
24        $user = ($data['userManager'] ?? null) instanceof IUser ? $data['userManager'] : null;
25        $requestOverrides = $this->getOverrides($data);
26        $activeContext = $this->extractActiveContext($data);
27        $resolvedPolicy = $activeContext === null
28            ? $this->policyService->resolveForUser(SignatureFlowPolicy::KEY, $user, $requestOverrides)
29            : $this->policyService->resolveForUser(SignatureFlowPolicy::KEY, $user, $requestOverrides, $activeContext);
30        $this->assertOverrideAllowed($requestOverrides, $resolvedPolicy);
31        $file->setSignatureFlowEnum(SignatureFlow::from((string)$resolvedPolicy->getEffectiveValue()));
32        $this->storePolicySnapshot($file, $resolvedPolicy);
33    }
34
35    #[\Override]
36    public function sync(FileEntity $file, array $data): void {
37        $requestOverrides = $this->getOverrides($data);
38        if ($this->isPolicySnapshotFrozen($file, SignatureFlowPolicy::KEY)) {
39            $this->assertFrozenFlowIsKept($file, $requestOverrides);
40            return;
41        }
42
43        $activeContext = $this->extractActiveContext($data);
44        $resolvedPolicy = $activeContext === null
45            ? $this->policyService->resolveForUserId(SignatureFlowPolicy::KEY, $file->getUserId(), $requestOverrides)
46            : $this->policyService->resolveForUserId(SignatureFlowPolicy::KEY, $file->getUserId(), $requestOverrides, $activeContext);
47        $this->assertOverrideAllowed($requestOverrides, $resolvedPolicy);
48        $newFlow = SignatureFlow::from((string)$resolvedPolicy->getEffectiveValue());
49        $metadataBeforeUpdate = $file->getMetadata() ?? [];
50        $this->storePolicySnapshot($file, $resolvedPolicy);
51        $metadataChanged = ($file->getMetadata() ?? []) !== $metadataBeforeUpdate;
52
53        if ($file->getSignatureFlowEnum() !== $newFlow || $metadataChanged) {
54            $file->setSignatureFlowEnum($newFlow);
55            $this->fileService->update($file);
56        }
57    }
58
59    #[\Override]
60    public function supportsCoreFlowSync(): bool {
61        return true;
62    }
63
64    /** @return array<string, string> */
65    private function getOverrides(array $data): array {
66        return $this->extractSinglePolicyOverride($data, SignatureFlowPolicy::KEY);
67    }
68
69    /** @param array<string, string> $requestOverrides */
70    private function assertOverrideAllowed(array $requestOverrides, ResolvedPolicy $resolvedPolicy): void {
71        $this->assertRequestOverrideAllowed($requestOverrides, $resolvedPolicy, 'Signature flow override is blocked by %s.');
72    }
73
74    /**
75     * A frozen request keeps the flow it was sent with. A client may still send
76     * that same flow along an unrelated update, which changes nothing.
77     *
78     * @param array<string, ?string> $requestOverrides
79     */
80    private function assertFrozenFlowIsKept(FileEntity $file, array $requestOverrides): void {
81        $submittedFlow = $requestOverrides[SignatureFlowPolicy::KEY] ?? null;
82        if ($submittedFlow === null || $submittedFlow === $file->getSignatureFlowEnum()->value) {
83            return;
84        }
85
86        throw new LibresignException(
87            // TRANSLATORS Error shown when someone tries to change whether signers sign in order after the signing flow already started.
88            $this->l10n?->t('The signing order cannot be changed after the signing flow has started.')
89                ?? 'The signing order cannot be changed after the signing flow has started.',
90            Http::STATUS_UNPROCESSABLE_ENTITY,
91        );
92    }
93}