Skip to content

Commit d076967

Browse files
authored
Merge pull request #6907 from LibreSign/fix/aggregate-visible-elements-by-files
fix: aggregate visible elements by files
2 parents 09b11d1 + 059ac3c commit d076967

7 files changed

Lines changed: 391 additions & 12 deletions

File tree

lib/Service/IdentifyMethod/AbstractIdentifyMethod.php

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
namespace OCA\Libresign\Service\IdentifyMethod;
1010

1111
use DateTime;
12-
use DateTimeInterface;
1312
use InvalidArgumentException;
1413
use OCA\Libresign\AppInfo\Application;
1514
use OCA\Libresign\Db\IdentifyMethod;
@@ -237,7 +236,7 @@ protected function throwIfInvalidToken(): void {
237236

238237
protected function renewSession(): void {
239238
$this->identifyService->getSessionService()->setIdentifyMethodId($this->getEntity()->getId());
240-
$renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
239+
$renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
241240
if ($renewalInterval <= 0) {
242241
return;
243242
}
@@ -255,7 +254,7 @@ protected function updateIdentifiedAt(): void {
255254
}
256255

257256
protected function throwIfRenewalIntervalExpired(): void {
258-
$renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
257+
$renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
259258
if ($renewalInterval <= 0) {
260259
return;
261260
}
@@ -268,24 +267,17 @@ protected function throwIfRenewalIntervalExpired(): void {
268267
}
269268
$createdAt = $signRequest->getCreatedAt();
270269
$lastAttempt = $this->getEntity()->getLastAttemptDate();
270+
$identifiedAt = $this->getEntity()->getIdentifiedAtDate();
271271
$lastActionDate = max(
272272
$startTime,
273273
$createdAt,
274274
$lastAttempt,
275+
$identifiedAt,
275276
);
276277
$now = $this->identifyService->getTimeFactory()->getDateTime();
277-
$this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Times', [
278-
'renewalInterval' => $renewalInterval,
279-
'startTime' => $startTime,
280-
'createdAt' => $createdAt,
281-
'lastAttempt' => $lastAttempt,
282-
'lastActionDate' => $lastActionDate,
283-
'now' => $now->format(DateTimeInterface::ATOM),
284-
]);
285278
$endRenewal = (clone $lastActionDate)
286279
->add(new \DateInterval('PT' . $renewalInterval . 'S'));
287280
if ($endRenewal < $now) {
288-
$this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Exception');
289281
if ($this->getName() === 'email') {
290282
$blur = new Blur($this->getEntity()->getIdentifierValue());
291283
throw new LibresignException(json_encode([
@@ -314,6 +306,12 @@ private function getRenewAction(): int {
314306
};
315307
}
316308

309+
private function getRuntimeConfigInt(string $key, int $default): int {
310+
$appConfig = $this->identifyService->getAppConfig();
311+
$appConfig->clearCache(true);
312+
return (int)$appConfig->getValueInt(Application::APP_ID, $key, $default);
313+
}
314+
317315
protected function throwIfAlreadySigned(): void {
318316
$signRequest = $this->identifyService->getSignRequestMapper()->getById($this->getEntity()->getSignRequestId());
319317
$fileEntity = $this->identifyService->getFileMapper()->getById($signRequest->getFileId());

lib/Service/SessionService.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ public function getSignStartTime(): int {
2727
}
2828

2929
public function getSessionId(): string {
30+
if ($this->isAuthenticated()) {
31+
return $this->session->getId();
32+
}
33+
$uuid = $this->session->get('libresign-uuid');
34+
if (is_string($uuid) && $uuid !== '') {
35+
return $uuid;
36+
}
3037
return $this->session->getId();
3138
}
3239

src/components/Request/VisibleElements.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,25 @@ export default {
213213
})
214214
const childFiles = response?.data?.ocs?.data?.data || []
215215
this.document.files = Array.isArray(childFiles) ? childFiles : []
216+
217+
const allVisibleElements = this.aggregateVisibleElementsByFiles(this.document.files)
218+
if (allVisibleElements.length > 0) {
219+
this.document.visibleElements = allVisibleElements
220+
}
221+
},
222+
aggregateVisibleElementsByFiles(files) {
223+
if (!Array.isArray(files) || files.length === 0) {
224+
return []
225+
}
226+
227+
const allVisibleElements = []
228+
files.forEach(file => {
229+
if (Array.isArray(file?.visibleElements)) {
230+
allVisibleElements.push(...file.visibleElements)
231+
}
232+
})
233+
234+
return allVisibleElements
216235
},
217236
buildFilePagesMap() {
218237
this.filePagesMap = {}

src/tests/components/Request/VisibleElements.spec.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,4 +602,101 @@ describe('VisibleElements Component - Business Rules', () => {
602602
expect(showError).toHaveBeenCalledWith('save failed')
603603
})
604604
})
605+
606+
describe('RULE: aggregateVisibleElementsByFiles', () => {
607+
it.each([
608+
{
609+
label: 'undefined input',
610+
input: undefined,
611+
expected: [],
612+
},
613+
{
614+
label: 'null input',
615+
input: null,
616+
expected: [],
617+
},
618+
{
619+
label: 'empty array',
620+
input: [],
621+
expected: [],
622+
},
623+
{
624+
label: 'mixed files with invalid entries',
625+
input: [
626+
{ id: 545, visibleElements: [{ elementId: 185, fileId: 545 }] },
627+
{ id: 999, visibleElements: null },
628+
{ id: 546, visibleElements: [{ elementId: 186, fileId: 546 }] },
629+
],
630+
expected: [
631+
{ elementId: 185, fileId: 545 },
632+
{ elementId: 186, fileId: 546 },
633+
],
634+
},
635+
{
636+
label: 'preserves order when a file has multiple elements',
637+
input: [
638+
{
639+
id: 100,
640+
visibleElements: [
641+
{ elementId: 1, fileId: 100 },
642+
{ elementId: 2, fileId: 100 },
643+
],
644+
},
645+
{ id: 200, visibleElements: [{ elementId: 3, fileId: 200 }] },
646+
],
647+
expected: [
648+
{ elementId: 1, fileId: 100 },
649+
{ elementId: 2, fileId: 100 },
650+
{ elementId: 3, fileId: 200 },
651+
],
652+
},
653+
])('handles $label', ({ input, expected }) => {
654+
expect(wrapper.vm.aggregateVisibleElementsByFiles(input)).toEqual(expected)
655+
})
656+
})
657+
658+
describe('RULE: fetchFiles updates document files and visible elements', () => {
659+
it.each([
660+
{
661+
label: 'applies aggregated visible elements when available',
662+
childFiles: [
663+
{ id: 545, name: 'file1.pdf', visibleElements: [{ elementId: 185, fileId: 545 }] },
664+
{ id: 546, name: 'file2.pdf', visibleElements: [{ elementId: 186, fileId: 546 }] },
665+
],
666+
initialVisibleElements: [{ elementId: 999, fileId: 1 }],
667+
expectedVisibleElements: [
668+
{ elementId: 185, fileId: 545 },
669+
{ elementId: 186, fileId: 546 },
670+
],
671+
},
672+
{
673+
label: 'keeps existing visibleElements when aggregated result is empty',
674+
childFiles: [
675+
{ id: 545, name: 'file1.pdf', visibleElements: [] },
676+
{ id: 546, name: 'file2.pdf' },
677+
],
678+
initialVisibleElements: [{ elementId: 999, fileId: 1 }],
679+
expectedVisibleElements: [{ elementId: 999, fileId: 1 }],
680+
},
681+
])('$label', async ({ childFiles, initialVisibleElements, expectedVisibleElements }) => {
682+
filesStore.files[1].id = 544
683+
filesStore.files[1].files = []
684+
filesStore.files[1].visibleElements = initialVisibleElements
685+
686+
axios.get.mockResolvedValue({
687+
data: {
688+
ocs: {
689+
data: {
690+
data: childFiles,
691+
},
692+
},
693+
},
694+
})
695+
696+
await wrapper.vm.fetchFiles()
697+
698+
expect(wrapper.vm.document.files).toEqual(childFiles)
699+
expect(wrapper.vm.document.visibleElements).toEqual(expectedVisibleElements)
700+
})
701+
})
605702
})

tests/integration/features/account/signature.feature

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,22 @@ Feature: account/signature
238238
When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/<NODE_ID>"
239239
Then the response should have a status code 200
240240

241+
Scenario: CRUD of signature element authenticated with public sign header
242+
Given user "signer1" exists
243+
And as user "signer1"
244+
And set the custom http header "libresign-sign-request-uuid" with "11111111-1111-1111-1111-111111111111" as value to next request
245+
When sending "post" to ocs "/apps/libresign/api/v1/signature/elements"
246+
| elements | [{"type":"signature","file":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}}] |
247+
Then the response should have a status code 200
248+
When sending "get" to ocs "/apps/libresign/api/v1/signature/elements"
249+
Then the response should be a JSON array with the following mandatory values
250+
| key | value |
251+
| (jq).ocs.data.elements\|length | 1 |
252+
| (jq).ocs.data.elements[0].type | signature |
253+
And fetch field "(NODE_ID)ocs.data.elements.0.file.nodeId" from previous JSON response
254+
When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/<NODE_ID>"
255+
Then the response should have a status code 200
256+
241257
Scenario: CRUD of signature element to signer by email without account
242258
Given run the command "config:app:set guests whitelist --value=libresign" with result code 0
243259
And run the command "libresign:configure:openssl --cn test" with result code 0

0 commit comments

Comments
 (0)