Skip to content

Commit da3d232

Browse files
committed
feat(policy): migrate identification_documents to canonical policy stack
1 parent c650c04 commit da3d232

12 files changed

Lines changed: 402 additions & 88 deletions

File tree

lib/Service/File/SettingsLoader.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@
1414
use OCA\Libresign\Db\IdDocsMapper;
1515
use OCA\Libresign\Db\SignRequest;
1616
use OCA\Libresign\Enum\FileStatus;
17-
use OCA\Libresign\ResponseDefinitions;
1817
use OCA\Libresign\Service\IdDocsPolicyService;
1918
use OCA\Libresign\Service\IdentifyMethodService;
19+
use OCA\Libresign\Service\Policy\PolicyService;
20+
use OCA\Libresign\Service\Policy\Provider\IdentificationDocuments\IdentificationDocumentsPolicy;
2021
use OCP\IAppConfig;
2122
use OCP\IGroupManager;
2223
use OCP\IUser;
23-
use stdClass;
2424

25-
/**
26-
* @psalm-import-type LibresignSettings from ResponseDefinitions
27-
*/
2825
class SettingsLoader {
2926
public const IDENTIFICATION_DOCUMENTS_DISABLED = 0;
3027
public const IDENTIFICATION_DOCUMENTS_NEED_SEND = 1;
@@ -34,6 +31,7 @@ class SettingsLoader {
3431
public function __construct(
3532
private AccountSettingsProvider $accountSettingsProvider,
3633
private IdDocsPolicyService $idDocsPolicyService,
34+
private PolicyService $policyService,
3735
private IAppConfig $appConfig,
3836
private IGroupManager $groupManager,
3937
private IdDocsMapper $idDocsMapper,
@@ -42,7 +40,7 @@ public function __construct(
4240
}
4341

4442
public function loadSettings(
45-
stdClass $fileData,
43+
\stdClass $fileData,
4644
FileResponseOptions $options,
4745
): void {
4846
if (!$options->isShowSettings()) {
@@ -69,7 +67,7 @@ public function loadSettings(
6967
}
7068
}
7169

72-
private function loadApproverSignatureMethods(stdClass $fileData): void {
70+
private function loadApproverSignatureMethods(\stdClass $fileData): void {
7371
try {
7472
$idDocs = $this->idDocsMapper->getByFileId($fileData->id);
7573
$signRequestId = $idDocs->getSignRequestId();
@@ -84,7 +82,7 @@ private function loadApproverSignatureMethods(stdClass $fileData): void {
8482
}
8583

8684
public function getIdentificationDocumentsStatus(?IUser $user = null, ?SignRequest $signRequest = null): int {
87-
if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
85+
if (!$this->isIdentificationDocumentsEnabled($user)) {
8886
return self::IDENTIFICATION_DOCUMENTS_DISABLED;
8987
}
9088

@@ -131,12 +129,33 @@ private function calculateStatusFromFiles(?array $files): int {
131129
return self::IDENTIFICATION_DOCUMENTS_APPROVED;
132130
}
133131

132+
private function isIdentificationDocumentsEnabled(?IUser $user): bool {
133+
$resolved = $user
134+
? $this->policyService->resolveForUser(IdentificationDocumentsPolicy::KEY, $user)
135+
: $this->policyService->resolve(IdentificationDocumentsPolicy::KEY);
136+
137+
$value = $resolved->getEffectiveValue();
138+
if (is_bool($value)) {
139+
return $value;
140+
}
141+
142+
if (is_int($value)) {
143+
return $value !== 0;
144+
}
145+
146+
if (is_string($value)) {
147+
return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true);
148+
}
149+
150+
return (bool)$value;
151+
}
152+
134153
/**
135154
* Get user identification documents settings
136155
* These are user-specific settings, not file-specific
137-
* Always returns complete LibresignSettings with defaults
156+
* Always returns complete settings payload with defaults.
138157
*
139-
* @psalm-return LibresignSettings
158+
* @return array<string, mixed>
140159
*/
141160
public function getUserIdentificationSettings(?IUser $user, ?SignRequest $signRequest = null): array {
142161
$status = $this->getIdentificationDocumentsStatus($user, $signRequest);

lib/Service/IdDocsPolicyService.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@
88

99
namespace OCA\Libresign\Service;
1010

11-
use OCA\Libresign\AppInfo\Application;
1211
use OCA\Libresign\Db\IdDocsMapper;
1312
use OCA\Libresign\Enum\FileStatus;
1413
use OCA\Libresign\Helper\ValidateHelper;
14+
use OCA\Libresign\Service\Policy\PolicyService;
15+
use OCA\Libresign\Service\Policy\Provider\IdentificationDocuments\IdentificationDocumentsPolicy;
1516
use OCP\AppFramework\Db\DoesNotExistException;
16-
use OCP\IAppConfig;
1717
use OCP\IUser;
1818

1919
class IdDocsPolicyService {
2020
public function __construct(
21-
private IAppConfig $appConfig,
21+
private PolicyService $policyService,
2222
private ValidateHelper $validateHelper,
2323
private IdDocsMapper $idDocsMapper,
2424
) {
2525
}
2626

2727
public function canApproverSignIdDoc(IUser $user, int $fileId, int $status): bool {
28-
if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
28+
if (!$this->isIdentificationDocumentsEnabled($user)) {
2929
return false;
3030
}
3131
if (!$this->validateHelper->userCanApproveValidationDocuments($user, false)) {
@@ -42,4 +42,22 @@ public function canApproverSignIdDoc(IUser $user, int $fileId, int $status): boo
4242
return false;
4343
}
4444
}
45+
46+
private function isIdentificationDocumentsEnabled(IUser $user): bool {
47+
$value = $this->policyService->resolveForUser(IdentificationDocumentsPolicy::KEY, $user)->getEffectiveValue();
48+
49+
if (is_bool($value)) {
50+
return $value;
51+
}
52+
53+
if (is_int($value)) {
54+
return $value !== 0;
55+
}
56+
57+
if (is_string($value)) {
58+
return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true);
59+
}
60+
61+
return (bool)$value;
62+
}
4563
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Libresign\Service\Policy\Provider\IdentificationDocuments;
10+
11+
use OCA\Libresign\Service\Policy\Contract\IPolicyDefinition;
12+
use OCA\Libresign\Service\Policy\Contract\IPolicyDefinitionProvider;
13+
use OCA\Libresign\Service\Policy\Model\PolicySpec;
14+
15+
final class IdentificationDocumentsPolicy implements IPolicyDefinitionProvider {
16+
public const KEY = 'identification_documents';
17+
public const SYSTEM_APP_CONFIG_KEY = 'identification_documents';
18+
19+
#[\Override]
20+
public function keys(): array {
21+
return [
22+
self::KEY,
23+
];
24+
}
25+
26+
#[\Override]
27+
public function get(string|\BackedEnum $policyKey): IPolicyDefinition {
28+
return match ($this->normalizePolicyKey($policyKey)) {
29+
self::KEY => new PolicySpec(
30+
key: self::KEY,
31+
defaultSystemValue: false,
32+
allowedValues: [
33+
false,
34+
true,
35+
],
36+
normalizer: static function (mixed $rawValue): mixed {
37+
if (is_bool($rawValue)) {
38+
return $rawValue;
39+
}
40+
41+
if (is_int($rawValue)) {
42+
return $rawValue !== 0;
43+
}
44+
45+
if (is_string($rawValue)) {
46+
$value = strtolower(trim($rawValue));
47+
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
48+
return true;
49+
}
50+
51+
if (in_array($value, ['0', 'false', 'no', 'off', ''], true)) {
52+
return false;
53+
}
54+
}
55+
56+
return (bool)$rawValue;
57+
},
58+
appConfigKey: self::SYSTEM_APP_CONFIG_KEY,
59+
),
60+
default => throw new \InvalidArgumentException('Unknown policy key: ' . $this->normalizePolicyKey($policyKey)),
61+
};
62+
}
63+
64+
private function normalizePolicyKey(string|\BackedEnum $policyKey): string {
65+
if ($policyKey instanceof \BackedEnum) {
66+
return (string)$policyKey->value;
67+
}
68+
69+
return $policyKey;
70+
}
71+
}

lib/Service/Policy/Provider/PolicyProviders.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use OCA\Libresign\Service\Policy\Provider\DocMdp\DocMdpPolicy;
1212
use OCA\Libresign\Service\Policy\Provider\Footer\FooterPolicy;
13+
use OCA\Libresign\Service\Policy\Provider\IdentificationDocuments\IdentificationDocumentsPolicy;
1314
use OCA\Libresign\Service\Policy\Provider\RequestSignGroups\RequestSignGroupsPolicy;
1415
use OCA\Libresign\Service\Policy\Provider\Signature\SignatureFlowPolicy;
1516

@@ -20,5 +21,6 @@ final class PolicyProviders {
2021
DocMdpPolicy::KEY => DocMdpPolicy::class,
2122
RequestSignGroupsPolicy::KEY => RequestSignGroupsPolicy::class,
2223
SignatureFlowPolicy::KEY => SignatureFlowPolicy::class,
24+
IdentificationDocumentsPolicy::KEY => IdentificationDocumentsPolicy::class,
2325
];
2426
}

src/tests/views/Settings/IdentificationDocuments.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ describe('IdentificationDocuments', () => {
4444

4545
it('saves groups on update:modelValue', async () => {
4646
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => {
47-
if (key === 'identification_documents') {
48-
return true
49-
}
5047
if (key === 'approval_group') {
5148
return []
5249
}
50+
if (key === 'effective_policies') {
51+
return {
52+
policies: {
53+
identification_documents: {
54+
effectiveValue: true,
55+
},
56+
},
57+
}
58+
}
5359
return fallback
5460
})
5561

@@ -74,7 +80,6 @@ describe('IdentificationDocuments', () => {
7480
global: {
7581
stubs: {
7682
NcSettingsSection: { template: '<div><slot /></div>' },
77-
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
7883
NcSelect: {
7984
name: 'NcSelect',
8085
props: ['modelValue'],

src/views/Settings/IdentificationDocuments.vue

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@
55
<template>
66
<NcSettingsSection
77
:name="t('libresign', 'Identification documents')"
8-
:description="t('libresign', 'The flow of identification documents will make it mandatory for anyone who must sign a file, to send their identification documents, this, in order for them to be approved by some member of the approval group. The user can only create the certificate after these are approved.')">
9-
<NcCheckboxRadioSwitch type="switch"
10-
v-model="identificationDocumentsFlowEnabled"
11-
@update:modelValue="saveIdentificationDocumentsStatus">
12-
{{ t('libresign', 'Enable identification documents flow') }}
13-
</NcCheckboxRadioSwitch>
14-
<p v-if="identificationDocumentsFlowEnabled">
15-
{{ t('libresign', 'Select authorized groups that can request to sign documents. Admin group is the default group and doesn\'t need to be defined.') }}
16-
<br>
17-
<NcSelect :key="idApprovalGroupsKey"
18-
v-model="approvalGroups"
19-
label="displayname"
20-
:no-wrap="false"
21-
:aria-label-combobox="description"
22-
:close-on-select="false"
23-
:disabled="loadingGroups"
24-
:loading="loadingGroups"
25-
:multiple="true"
26-
:options="groups"
27-
:searchable="true"
28-
:show-no-options="false"
29-
@search-change="searchGroup"
30-
@update:modelValue="saveApprovalGroups" />
8+
:description="t('libresign', 'Configure which groups can approve submitted identification documents. Admin group members can always approve.')">
9+
<p>
10+
{{ t('libresign', 'The Identification documents flow itself is managed in Policies.') }}
11+
</p>
12+
<p>
13+
{{ t('libresign', 'Approval groups are used only when the effective policy enables this flow.') }}
14+
</p>
15+
<NcSelect :key="idApprovalGroupsKey"
16+
v-model="approvalGroups"
17+
label="displayname"
18+
:no-wrap="false"
19+
:aria-label-combobox="description"
20+
:close-on-select="false"
21+
:disabled="loadingGroups || !identificationDocumentsFlowEnabled"
22+
:loading="loadingGroups"
23+
:multiple="true"
24+
:options="groups"
25+
:searchable="true"
26+
:show-no-options="false"
27+
@search-change="searchGroup"
28+
@update:modelValue="saveApprovalGroups" />
29+
<p v-if="!identificationDocumentsFlowEnabled" class="identification-documents-content__hint">
30+
{{ t('libresign', 'This list is disabled because the effective policy currently disables identification documents flow.') }}
3131
</p>
3232
</NcSettingsSection>
3333
</template>
@@ -38,7 +38,6 @@ import { generateOcsUrl } from '@nextcloud/router'
3838
import { t } from '@nextcloud/l10n'
3939
import { computed, onMounted, ref } from 'vue'
4040
41-
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
4241
import NcSelect from '@nextcloud/vue/components/NcSelect'
4342
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
4443
@@ -54,8 +53,26 @@ type Group = {
5453
}
5554
5655
const approvalGroupState = loadState('libresign', 'approval_group', ['admin'])
57-
const identificationDocumentsState = loadState<unknown>('libresign', 'identification_documents', false)
58-
const identificationDocumentsFlowEnabled = ref(identificationDocumentsState === true || identificationDocumentsState === '1')
56+
const effectivePoliciesState = loadState<{ policies?: Record<string, { effectiveValue?: unknown }> }>('libresign', 'effective_policies', { policies: {} })
57+
58+
function resolveEnabledValue(value: unknown): boolean {
59+
if (typeof value === 'boolean') {
60+
return value
61+
}
62+
63+
if (typeof value === 'number') {
64+
return value !== 0
65+
}
66+
67+
if (typeof value === 'string') {
68+
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase())
69+
}
70+
71+
return false
72+
}
73+
74+
const effectivePolicies = effectivePoliciesState?.policies ?? {}
75+
const identificationDocumentsFlowEnabled = ref(resolveEnabledValue(effectivePolicies.identification_documents?.effectiveValue))
5976
const approvalGroupIds = ref<string[]>(Array.isArray(approvalGroupState) ? approvalGroupState : ['admin'])
6077
const approvalGroups = ref<Array<Group | string>>([])
6178
const groups = ref<Group[]>([])
@@ -68,10 +85,6 @@ function syncApprovalGroupsFromState() {
6885
approvalGroups.value = groups.value.filter((group) => approvalGroupIds.value.indexOf(group.id) !== -1)
6986
}
7087
71-
function saveIdentificationDocumentsStatus() {
72-
OCP.AppConfig.setValue('libresign', 'identification_documents', identificationDocumentsFlowEnabled.value ? '1' : '0')
73-
}
74-
7588
function saveApprovalGroups() {
7689
const listOfInputGroupsSelected = JSON.stringify(approvalGroups.value.map((group) => {
7790
if (typeof group === 'object') {
@@ -106,7 +119,7 @@ onMounted(async () => {
106119
})
107120
</script>
108121
<style scoped>
109-
.identification-documents-content{
122+
.identification-documents-content {
110123
display: flex;
111124
flex-direction: column;
112125
}

0 commit comments

Comments
 (0)