Skip to content
Merged
22 changes: 10 additions & 12 deletions lib/Service/IdentifyMethod/AbstractIdentifyMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
namespace OCA\Libresign\Service\IdentifyMethod;

use DateTime;
use DateTimeInterface;
use InvalidArgumentException;
use OCA\Libresign\AppInfo\Application;
use OCA\Libresign\Db\IdentifyMethod;
Expand Down Expand Up @@ -219,7 +218,7 @@ protected function throwIfInvalidToken(): void {

protected function renewSession(): void {
$this->identifyService->getSessionService()->setIdentifyMethodId($this->getEntity()->getId());
$renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
$renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
if ($renewalInterval <= 0) {
return;
}
Expand All @@ -237,7 +236,7 @@ protected function updateIdentifiedAt(): void {
}

protected function throwIfRenewalIntervalExpired(): void {
$renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
$renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL);
if ($renewalInterval <= 0) {
return;
}
Expand All @@ -250,24 +249,17 @@ protected function throwIfRenewalIntervalExpired(): void {
}
$createdAt = $signRequest->getCreatedAt();
$lastAttempt = $this->getEntity()->getLastAttemptDate();
$identifiedAt = $this->getEntity()->getIdentifiedAtDate();
$lastActionDate = max(
$startTime,
$createdAt,
$lastAttempt,
$identifiedAt,
);
$now = $this->identifyService->getTimeFactory()->getDateTime();
$this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Times', [
'renewalInterval' => $renewalInterval,
'startTime' => $startTime,
'createdAt' => $createdAt,
'lastAttempt' => $lastAttempt,
'lastActionDate' => $lastActionDate,
'now' => $now->format(DateTimeInterface::ATOM),
]);
$endRenewal = (clone $lastActionDate)
->add(new \DateInterval('PT' . $renewalInterval . 'S'));
if ($endRenewal < $now) {
$this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Exception');
if ($this->getName() === 'email') {
$blur = new Blur($this->getEntity()->getIdentifierValue());
throw new LibresignException(json_encode([
Expand Down Expand Up @@ -296,6 +288,12 @@ private function getRenewAction(): int {
};
}

private function getRuntimeConfigInt(string $key, int $default): int {
$appConfig = $this->identifyService->getAppConfig();
$appConfig->clearCache(true);
return (int)$appConfig->getValueInt(Application::APP_ID, $key, $default);
}

protected function throwIfAlreadySigned(): void {
$signRequest = $this->identifyService->getSignRequestMapper()->getById($this->getEntity()->getSignRequestId());
$fileEntity = $this->identifyService->getFileMapper()->getById($signRequest->getFileId());
Expand Down
7 changes: 7 additions & 0 deletions lib/Service/SessionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ public function getSignStartTime(): int {
}

public function getSessionId(): string {
if ($this->isAuthenticated()) {
return $this->session->getId();
}
$uuid = $this->session->get('libresign-uuid');
if (is_string($uuid) && $uuid !== '') {
return $uuid;
}
return $this->session->getId();
}

Expand Down
19 changes: 19 additions & 0 deletions src/components/Request/VisibleElements.vue
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,25 @@ export default {
})
const childFiles = response?.data?.ocs?.data?.data || []
this.document.files = Array.isArray(childFiles) ? childFiles : []

const allVisibleElements = this.aggregateVisibleElementsByFiles(this.document.files)
if (allVisibleElements.length > 0) {
this.document.visibleElements = allVisibleElements
}
},
aggregateVisibleElementsByFiles(files) {
if (!Array.isArray(files) || files.length === 0) {
return []
}

const allVisibleElements = []
files.forEach(file => {
if (Array.isArray(file?.visibleElements)) {
allVisibleElements.push(...file.visibleElements)
}
})

return allVisibleElements
},
buildFilePagesMap() {
this.filePagesMap = {}
Expand Down
97 changes: 97 additions & 0 deletions src/tests/components/Request/VisibleElements.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,4 +602,101 @@ describe('VisibleElements Component - Business Rules', () => {
expect(showError).toHaveBeenCalledWith('save failed')
})
})

describe('RULE: aggregateVisibleElementsByFiles', () => {
it.each([
{
label: 'undefined input',
input: undefined,
expected: [],
},
{
label: 'null input',
input: null,
expected: [],
},
{
label: 'empty array',
input: [],
expected: [],
},
{
label: 'mixed files with invalid entries',
input: [
{ id: 545, visibleElements: [{ elementId: 185, fileId: 545 }] },
{ id: 999, visibleElements: null },
{ id: 546, visibleElements: [{ elementId: 186, fileId: 546 }] },
],
expected: [
{ elementId: 185, fileId: 545 },
{ elementId: 186, fileId: 546 },
],
},
{
label: 'preserves order when a file has multiple elements',
input: [
{
id: 100,
visibleElements: [
{ elementId: 1, fileId: 100 },
{ elementId: 2, fileId: 100 },
],
},
{ id: 200, visibleElements: [{ elementId: 3, fileId: 200 }] },
],
expected: [
{ elementId: 1, fileId: 100 },
{ elementId: 2, fileId: 100 },
{ elementId: 3, fileId: 200 },
],
},
])('handles $label', ({ input, expected }) => {
expect(wrapper.vm.aggregateVisibleElementsByFiles(input)).toEqual(expected)
})
})

describe('RULE: fetchFiles updates document files and visible elements', () => {
it.each([
{
label: 'applies aggregated visible elements when available',
childFiles: [
{ id: 545, name: 'file1.pdf', visibleElements: [{ elementId: 185, fileId: 545 }] },
{ id: 546, name: 'file2.pdf', visibleElements: [{ elementId: 186, fileId: 546 }] },
],
initialVisibleElements: [{ elementId: 999, fileId: 1 }],
expectedVisibleElements: [
{ elementId: 185, fileId: 545 },
{ elementId: 186, fileId: 546 },
],
},
{
label: 'keeps existing visibleElements when aggregated result is empty',
childFiles: [
{ id: 545, name: 'file1.pdf', visibleElements: [] },
{ id: 546, name: 'file2.pdf' },
],
initialVisibleElements: [{ elementId: 999, fileId: 1 }],
expectedVisibleElements: [{ elementId: 999, fileId: 1 }],
},
])('$label', async ({ childFiles, initialVisibleElements, expectedVisibleElements }) => {
filesStore.files[1].id = 544
filesStore.files[1].files = []
filesStore.files[1].visibleElements = initialVisibleElements

axios.get.mockResolvedValue({
data: {
ocs: {
data: {
data: childFiles,
},
},
},
})

await wrapper.vm.fetchFiles()

expect(wrapper.vm.document.files).toEqual(childFiles)
expect(wrapper.vm.document.visibleElements).toEqual(expectedVisibleElements)
})
})
})
16 changes: 16 additions & 0 deletions tests/integration/features/account/signature.feature
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,22 @@ Feature: account/signature
When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/<NODE_ID>"
Then the response should have a status code 200

Scenario: CRUD of signature element authenticated with public sign header
Given user "signer1" exists
And as user "signer1"
And set the custom http header "libresign-sign-request-uuid" with "11111111-1111-1111-1111-111111111111" as value to next request
When sending "post" to ocs "/apps/libresign/api/v1/signature/elements"
| elements | [{"type":"signature","file":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}}] |
Then the response should have a status code 200
When sending "get" to ocs "/apps/libresign/api/v1/signature/elements"
Then the response should be a JSON array with the following mandatory values
| key | value |
| (jq).ocs.data.elements\|length | 1 |
| (jq).ocs.data.elements[0].type | signature |
And fetch field "(NODE_ID)ocs.data.elements.0.file.nodeId" from previous JSON response
When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/<NODE_ID>"
Then the response should have a status code 200

Scenario: CRUD of signature element to signer by email without account
Given run the command "config:app:set guests whitelist --value=libresign" with result code 0
And run the command "libresign:configure:openssl --cn test" with result code 0
Expand Down
Loading
Loading