mirror of
https://github.com/LibreSign/libresign.git
synced 2025-12-18 05:20:45 +01:00
Merge pull request #6216 from LibreSign/feat/custom-message-for-signers
feat: custom message for signers
This commit is contained in:
commit
9a9ca2bba7
19 changed files with 367 additions and 21 deletions
|
|
@ -19,8 +19,10 @@ use OCP\AppFramework\Http\Attribute\ApiRoute;
|
|||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\Collaboration\Collaborators\ISearch;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
|
|
@ -36,6 +38,8 @@ class IdentifyAccountController extends AEnvironmentAwareController {
|
|||
private IURLGenerator $urlGenerator,
|
||||
private Email $identifyEmailMethod,
|
||||
private Account $identifyAccountMethod,
|
||||
private IUserManager $userManager,
|
||||
private IConfig $config,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
}
|
||||
|
|
@ -76,6 +80,7 @@ class IdentifyAccountController extends AEnvironmentAwareController {
|
|||
$return = $this->addHerselfAccount($return, $search);
|
||||
$return = $this->addHerselfEmail($return, $search);
|
||||
$return = $this->replaceShareTypeByMethod($return);
|
||||
$return = $this->addEmailNotificationPreference($return);
|
||||
$return = $this->excludeNotAllowed($return);
|
||||
|
||||
return new DataResponse($return);
|
||||
|
|
@ -239,4 +244,62 @@ class IdentifyAccountController extends AEnvironmentAwareController {
|
|||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function addEmailNotificationPreference(array $list): array {
|
||||
foreach ($list as $key => $item) {
|
||||
if ($item['method'] !== 'account') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = $this->userManager->get($item['id']);
|
||||
if ($user === null) {
|
||||
$list[$key]['acceptsEmailNotifications'] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = $user->getEMailAddress();
|
||||
if (empty($email)) {
|
||||
$list[$key]['acceptsEmailNotifications'] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isNotificationDisabledAtActivity($user->getUID(), 'libresign_file_to_sign')) {
|
||||
$list[$key]['acceptsEmailNotifications'] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$key]['acceptsEmailNotifications'] = true;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
private function isNotificationDisabledAtActivity(string $userId, string $type): bool {
|
||||
if (!class_exists(\OCA\Activity\UserSettings::class)) {
|
||||
return false;
|
||||
}
|
||||
$activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class);
|
||||
if ($activityUserSettings) {
|
||||
$manager = \OCP\Server::get(\OCP\Activity\IManager::class);
|
||||
try {
|
||||
$manager->getSettingById($type);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$adminSetting = $activityUserSettings->getAdminSetting('email', $type);
|
||||
if (!$adminSetting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$notificationSetting = $activityUserSettings->getUserSetting(
|
||||
$userId,
|
||||
'email',
|
||||
$type
|
||||
);
|
||||
if (!$notificationSetting) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class IdentifyMethodMapper extends QBMapper {
|
|||
->join('im', 'libresign_sign_request', 'sr',
|
||||
$qb->expr()->eq('sr.id', 'im.sign_request_id'),
|
||||
)
|
||||
->where($qb->expr()->neq('im.identifier_value', $qb->createNamedParameter('deleted_users')))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,10 +60,17 @@ class SignRequestMapper extends QBMapper {
|
|||
if (!isset($metadata['notify'])) {
|
||||
$this->firstNotification = true;
|
||||
}
|
||||
$metadata['notify'][] = [
|
||||
|
||||
$notificationEntry = [
|
||||
'method' => $method,
|
||||
'date' => time(),
|
||||
];
|
||||
|
||||
if (!empty($fromDatabase->getDescription())) {
|
||||
$notificationEntry['description'] = $fromDatabase->getDescription();
|
||||
}
|
||||
|
||||
$metadata['notify'][] = $notificationEntry;
|
||||
$fromDatabase->setMetadata($metadata);
|
||||
$this->update($fromDatabase);
|
||||
$this->db->commit();
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ class MailNotifyListener implements IEventListener {
|
|||
|
||||
$isFirstNotification = $this->signRequestMapper->incrementNotificationCounter($signRequest, 'mail');
|
||||
if ($isFirstNotification) {
|
||||
$this->mail->notifyUnsignedUser($signRequest, $email);
|
||||
$this->mail->notifyUnsignedUser($signRequest, $email, $signRequest->getDescription());
|
||||
return;
|
||||
}
|
||||
$this->mail->notifySignDataUpdated($signRequest, $email);
|
||||
$this->mail->notifySignDataUpdated($signRequest, $email, $signRequest->getDescription());
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
return;
|
||||
|
|
@ -174,6 +174,17 @@ class MailNotifyListener implements IEventListener {
|
|||
}
|
||||
$activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class);
|
||||
if ($activityUserSettings) {
|
||||
$manager = \OCP\Server::get(\OCP\Activity\IManager::class);
|
||||
try {
|
||||
$manager->getSettingById($type);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$adminSetting = $activityUserSettings->getAdminSetting('email', $type);
|
||||
if (!$adminSetting) {
|
||||
return true;
|
||||
}
|
||||
$notificationSetting = $activityUserSettings->getUserSetting(
|
||||
$userId,
|
||||
'email',
|
||||
|
|
|
|||
|
|
@ -210,13 +210,23 @@ class NotificationListener implements IEventListener {
|
|||
}
|
||||
$activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class);
|
||||
if ($activityUserSettings) {
|
||||
$manager = \OCP\Server::get(\OCP\Activity\IManager::class);
|
||||
try {
|
||||
$manager->getSettingById($type);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$adminSetting = $activityUserSettings->getAdminSetting('notification', $type);
|
||||
if (!$adminSetting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$notificationSetting = $activityUserSettings->getUserSetting(
|
||||
$userId,
|
||||
'notification',
|
||||
$type
|
||||
);
|
||||
// If setting is explicitly false, notifications are disabled
|
||||
// If setting is null/not configured, notifications are enabled by default
|
||||
if ($notificationSetting === false) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,16 @@ class TwofactorGatewayListener implements IEventListener {
|
|||
}
|
||||
|
||||
$isFirstNotification = $this->signRequestMapper->incrementNotificationCounter($signRequest, $entity->getIdentifierKey());
|
||||
|
||||
$message = '';
|
||||
if (!empty($signRequest->getDescription())) {
|
||||
$message = $signRequest->getDescription() . "\n\n";
|
||||
}
|
||||
|
||||
if ($isFirstNotification) {
|
||||
$message = $this->l10n->t('There is a document for you to sign. Access the link below:');
|
||||
$message .= $this->l10n->t('There is a document for you to sign. Access the link below:');
|
||||
} else {
|
||||
$message = $this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:');
|
||||
$message .= $this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:');
|
||||
}
|
||||
$message .= "\n";
|
||||
$link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $signRequest->getUuid()]);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ namespace OCA\Libresign;
|
|||
* email?: string,
|
||||
* account?: string,
|
||||
* },
|
||||
* displayName?: string,
|
||||
* description?: string,
|
||||
* notify?: non-negative-int,
|
||||
* signingOrder?: non-negative-int,
|
||||
* }
|
||||
* @psalm-type LibresignNewFile = array{
|
||||
|
|
@ -55,6 +58,7 @@ namespace OCA\Libresign;
|
|||
* subname: string,
|
||||
* shareType: 0|4,
|
||||
* icon?: 'icon-mail'|'icon-user',
|
||||
* acceptsEmailNotifications?: boolean,
|
||||
* }
|
||||
* @psalm-type LibresignPagination = array{
|
||||
* total: non-negative-int,
|
||||
|
|
|
|||
|
|
@ -45,12 +45,18 @@ class MailService {
|
|||
/**
|
||||
* @psalm-suppress MixedMethodCall
|
||||
*/
|
||||
public function notifySignDataUpdated(SignRequest $data, string $email): void {
|
||||
public function notifySignDataUpdated(SignRequest $data, string $email, ?string $description = null): void {
|
||||
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
|
||||
// TRANSLATORS The subject of the email that is sent after changes are made to the signature request that may affect something for the signer who will sign the document. Some possible reasons: URL for signature changed (when the URL expires), the person who requested the signature sent a notification
|
||||
$emailTemplate->setSubject($this->l10n->t('LibreSign: Changes into a file for you to sign'));
|
||||
$emailTemplate->addHeader();
|
||||
$emailTemplate->addHeading($this->l10n->t('File to sign'), false);
|
||||
|
||||
if (!empty($description)) {
|
||||
$emailTemplate->addBodyText($description);
|
||||
$emailTemplate->addBodyText('');
|
||||
}
|
||||
|
||||
$emailTemplate->addBodyText($this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:'));
|
||||
$link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $data->getUuid()]);
|
||||
$file = $this->getFileById($data->getFileId());
|
||||
|
|
@ -76,11 +82,17 @@ class MailService {
|
|||
/**
|
||||
* @psalm-suppress MixedMethodCall
|
||||
*/
|
||||
public function notifyUnsignedUser(SignRequest $data, string $email): void {
|
||||
public function notifyUnsignedUser(SignRequest $data, string $email, ?string $description = null): void {
|
||||
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
|
||||
$emailTemplate->setSubject($this->l10n->t('LibreSign: There is a file for you to sign'));
|
||||
$emailTemplate->addHeader();
|
||||
$emailTemplate->addHeading($this->l10n->t('File to sign'), false);
|
||||
|
||||
if (!empty($description)) {
|
||||
$emailTemplate->addBodyText($description);
|
||||
$emailTemplate->addBodyText('');
|
||||
}
|
||||
|
||||
$emailTemplate->addBodyText($this->l10n->t('There is a document for you to sign. Access the link below:'));
|
||||
$link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $data->getUuid()]);
|
||||
$file = $this->getFileById($data->getFileId());
|
||||
|
|
|
|||
|
|
@ -453,6 +453,9 @@
|
|||
"icon-mail",
|
||||
"icon-user"
|
||||
]
|
||||
},
|
||||
"acceptsEmailNotifications": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -514,6 +517,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"notify": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
},
|
||||
"signingOrder": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
|
|
|
|||
14
openapi.json
14
openapi.json
|
|
@ -383,6 +383,9 @@
|
|||
"icon-mail",
|
||||
"icon-user"
|
||||
]
|
||||
},
|
||||
"acceptsEmailNotifications": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -444,6 +447,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"notify": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
},
|
||||
"signingOrder": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,23 @@
|
|||
:error="nameHaveError"
|
||||
:helper-text="nameHelperText"
|
||||
@update:value="onNameChange" />
|
||||
|
||||
<div v-if="signerSelected && showCustomMessage" class="description-wrapper">
|
||||
<NcCheckboxRadioSwitch v-model:checked="enableCustomMessage"
|
||||
type="switch"
|
||||
@update:checked="onToggleCustomMessage">
|
||||
{{ t('libresign', 'Add custom message') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcTextArea v-if="enableCustomMessage"
|
||||
v-model="description"
|
||||
aria-describedby="description-input"
|
||||
maxlength="500"
|
||||
:label="t('libresign', 'Custom message')"
|
||||
:placeholder="t('libresign', 'Add a personal message for this signer')"
|
||||
:rows="3"
|
||||
resize="none" />
|
||||
</div>
|
||||
|
||||
<div class="identifySigner__footer">
|
||||
<div class="button-group">
|
||||
<NcButton @click="filesStore.disableIdentifySigner()">
|
||||
|
|
@ -48,8 +65,10 @@ import svgWhatsapp from '@mdi/svg/svg/whatsapp.svg?raw'
|
|||
import svgXmpp from '@mdi/svg/svg/xmpp.svg?raw'
|
||||
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcTextArea from '@nextcloud/vue/components/NcTextArea'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
|
||||
import SignerSelect from './SignerSelect.vue'
|
||||
|
|
@ -72,8 +91,10 @@ export default {
|
|||
name: 'IdentifySigner',
|
||||
components: {
|
||||
NcButton,
|
||||
NcCheckboxRadioSwitch,
|
||||
NcIconSvgWrapper,
|
||||
NcNoteCard,
|
||||
NcTextArea,
|
||||
NcTextField,
|
||||
SignerSelect,
|
||||
},
|
||||
|
|
@ -109,6 +130,8 @@ export default {
|
|||
nameHelperText: '',
|
||||
nameHaveError: false,
|
||||
displayName: '',
|
||||
description: '',
|
||||
enableCustomMessage: false,
|
||||
identify: '',
|
||||
signer: {},
|
||||
}
|
||||
|
|
@ -136,9 +159,17 @@ export default {
|
|||
}
|
||||
return methodConfig.friendly_name
|
||||
},
|
||||
showCustomMessage() {
|
||||
if (this.signer?.method === 'account') {
|
||||
return this.signer?.acceptsEmailNotifications === true
|
||||
}
|
||||
return !!this.signer?.method
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.displayName = this.signerToEdit.displayName ?? ''
|
||||
this.description = this.signerToEdit.description ?? ''
|
||||
this.enableCustomMessage = !!(this.signerToEdit.description)
|
||||
this.identify = this.signerToEdit.identify ?? this.signerToEdit.signRequestId ?? ''
|
||||
if (Object.keys(this.signerToEdit).length > 0 && this.signerToEdit.identifyMethods?.length) {
|
||||
const method = this.signerToEdit.identifyMethods[0]
|
||||
|
|
@ -161,6 +192,11 @@ export default {
|
|||
this.signer = signer ?? {}
|
||||
this.displayName = signer?.displayName ?? ''
|
||||
this.identify = signer?.id ?? ''
|
||||
|
||||
if (signer?.method === 'account' && signer?.acceptsEmailNotifications === false) {
|
||||
this.enableCustomMessage = false
|
||||
this.description = ''
|
||||
}
|
||||
},
|
||||
async saveSigner() {
|
||||
if (!this.signer?.method || !this.signer?.id) {
|
||||
|
|
@ -168,6 +204,7 @@ export default {
|
|||
}
|
||||
this.filesStore.signerUpdate({
|
||||
displayName: this.displayName,
|
||||
description: this.description.trim() || undefined,
|
||||
identify: this.identify,
|
||||
identifyMethods: [
|
||||
{
|
||||
|
|
@ -184,6 +221,7 @@ export default {
|
|||
}
|
||||
|
||||
this.displayName = ''
|
||||
this.description = ''
|
||||
this.identify = ''
|
||||
this.signer = {}
|
||||
this.filesStore.disableIdentifySigner()
|
||||
|
|
@ -198,6 +236,11 @@ export default {
|
|||
this.nameHelperText = t('libresign', 'Please enter signer name.')
|
||||
this.nameHaveError = true
|
||||
},
|
||||
onToggleCustomMessage(checked) {
|
||||
if (!checked) {
|
||||
this.description = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -224,6 +267,14 @@ export default {
|
|||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
.description-wrapper {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(textarea) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 100%;
|
||||
|
|
@ -234,6 +285,7 @@ export default {
|
|||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--color-main-background));
|
||||
padding-top: 24px;
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@
|
|||
<OrderNumericAscending :size="20" />
|
||||
</template>
|
||||
</NcActionInput>
|
||||
<NcActionButton v-if="canCustomizeMessage(signer)"
|
||||
:close-after-click="true"
|
||||
@click="customizeMessage(signer); closeActions()">
|
||||
<template #icon>
|
||||
<MessageText :size="20" />
|
||||
</template>
|
||||
{{ t('libresign', 'Customize message') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="canDelete(signer)"
|
||||
aria-label="Delete"
|
||||
:close-after-click="true"
|
||||
|
|
@ -44,9 +52,11 @@
|
|||
{{ t('libresign', 'Request signature') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="canSendReminder(signer)"
|
||||
icon="icon-comment"
|
||||
:close-after-click="true"
|
||||
@click="sendNotify(signer)">
|
||||
<template #icon>
|
||||
<Bell :size="20" />
|
||||
</template>
|
||||
{{ t('libresign', 'Send reminder') }}
|
||||
</NcActionButton>
|
||||
</template>
|
||||
|
|
@ -194,10 +204,12 @@ import svgSms from '@mdi/svg/svg/message-processing.svg?raw'
|
|||
import svgWhatsapp from '@mdi/svg/svg/whatsapp.svg?raw'
|
||||
import svgXmpp from '@mdi/svg/svg/xmpp.svg?raw'
|
||||
|
||||
import Bell from 'vue-material-design-icons/Bell.vue'
|
||||
import Delete from 'vue-material-design-icons/Delete.vue'
|
||||
import Draw from 'vue-material-design-icons/Draw.vue'
|
||||
import FileDocument from 'vue-material-design-icons/FileDocument.vue'
|
||||
import Information from 'vue-material-design-icons/Information.vue'
|
||||
import MessageText from 'vue-material-design-icons/MessageText.vue'
|
||||
import OrderNumericAscending from 'vue-material-design-icons/OrderNumericAscending.vue'
|
||||
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
||||
import Send from 'vue-material-design-icons/Send.vue'
|
||||
|
|
@ -261,10 +273,12 @@ export default {
|
|||
NcModal,
|
||||
NcNoteCard,
|
||||
NcDialog,
|
||||
Bell,
|
||||
Delete,
|
||||
Draw,
|
||||
FileDocument,
|
||||
Information,
|
||||
MessageText,
|
||||
OrderNumericAscending,
|
||||
Pencil,
|
||||
Send,
|
||||
|
|
@ -323,6 +337,20 @@ export default {
|
|||
return this.filesStore.canSave() && !signer.signed
|
||||
}
|
||||
},
|
||||
canCustomizeMessage() {
|
||||
return (signer) => {
|
||||
if (signer.signed || !signer.signRequestId || signer.me) {
|
||||
return false
|
||||
}
|
||||
|
||||
const method = signer.identifyMethods?.[0]?.method
|
||||
if (method === 'account' && !signer.acceptsEmailNotifications) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !!method
|
||||
}
|
||||
},
|
||||
canRequestSignature() {
|
||||
return (signer) => {
|
||||
if (!this.filesStore.canRequestSign
|
||||
|
|
@ -530,6 +558,10 @@ export default {
|
|||
}
|
||||
this.filesStore.enableIdentifySigner()
|
||||
},
|
||||
customizeMessage(signer) {
|
||||
this.signerToEdit = signer
|
||||
this.filesStore.enableIdentifySigner()
|
||||
},
|
||||
onTabChange(tabId) {
|
||||
if (this.activeTab !== tabId) {
|
||||
this.activeTab = tabId
|
||||
|
|
|
|||
|
|
@ -1584,6 +1584,7 @@ export type components = {
|
|||
shareType: 0 | 4;
|
||||
/** @enum {string} */
|
||||
icon?: "icon-mail" | "icon-user";
|
||||
acceptsEmailNotifications?: boolean;
|
||||
};
|
||||
IdentifyMethod: {
|
||||
/** @enum {string} */
|
||||
|
|
@ -1603,6 +1604,10 @@ export type components = {
|
|||
email?: string;
|
||||
account?: string;
|
||||
};
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
/** Format: int64 */
|
||||
notify?: number;
|
||||
/** Format: int64 */
|
||||
signingOrder?: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1128,6 +1128,7 @@ export type components = {
|
|||
shareType: 0 | 4;
|
||||
/** @enum {string} */
|
||||
icon?: "icon-mail" | "icon-user";
|
||||
acceptsEmailNotifications?: boolean;
|
||||
};
|
||||
IdentifyMethod: {
|
||||
/** @enum {string} */
|
||||
|
|
@ -1147,6 +1148,10 @@ export type components = {
|
|||
email?: string;
|
||||
account?: string;
|
||||
};
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
/** Format: int64 */
|
||||
notify?: number;
|
||||
/** Format: int64 */
|
||||
signingOrder?: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.rootCert.names[4].id | OU |
|
||||
| (jq).ocs.data.rootCert.names[4].value\|length | 2 |
|
||||
| (jq).ocs.data.rootCert.names[4].value[0] | Organizational Unit |
|
||||
| (jq).ocs.data.rootCert.names[4].value | (jq)any(.[]; test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$")) |
|
||||
| (jq).ocs.data.rootCert.names[4].value | (jq)any(.[]; test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$")) |
|
||||
| (jq).ocs.data.generated | true |
|
||||
|
||||
Scenario: Create root certificate with CFSSL engine using API
|
||||
|
|
@ -40,7 +40,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.rootCert.names[4].id | OU |
|
||||
| (jq).ocs.data.rootCert.names[4].value\|length | 2 |
|
||||
| (jq).ocs.data.rootCert.names[4].value[0] | Organizational Unit |
|
||||
| (jq).ocs.data.rootCert.names[4].value | (jq)any(.[]; test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$")) |
|
||||
| (jq).ocs.data.rootCert.names[4].value | (jq)any(.[]; test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$")) |
|
||||
| (jq).ocs.data.generated | true |
|
||||
|
||||
Scenario: Create pfx with success using CFSSL
|
||||
|
|
@ -77,7 +77,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data | (jq).name \|test("/ST=State of Company") |
|
||||
| (jq).ocs.data | (jq).name \|test("/L=City Name") |
|
||||
| (jq).ocs.data | (jq).name \|test("/O=Organization") |
|
||||
| (jq).ocs.data | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data | (jq).name \|test("/CN=account:signer1, signer1-displayname") |
|
||||
| (jq).ocs.data.issuer\|length | 6 |
|
||||
| (jq).ocs.data.issuer.CN | Common Name |
|
||||
|
|
@ -87,7 +87,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.issuer.O | Organization |
|
||||
| (jq).ocs.data.issuer.OU\|length | 2 |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[0] \|test("^Organization Unit$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.subject\|length | 6 |
|
||||
| (jq).ocs.data.subject.CN | (jq) .[0] \|test("^account:signer1$") |
|
||||
| (jq).ocs.data.subject.CN | (jq) .[1] \|test("^signer1-displayname$") |
|
||||
|
|
@ -97,7 +97,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.subject.O | Organization |
|
||||
| (jq).ocs.data.issuer.OU\|length | 2 |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[0] \|test("^Organization Unit$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.extensions.basicConstraints | CA:FALSE |
|
||||
| (jq).ocs.data.extensions.subjectAltName | email:signer@domain.test |
|
||||
| (jq).ocs.data.extensions.keyUsage | Digital Signature, Non Repudiation, Key Encipherment |
|
||||
|
|
@ -138,7 +138,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data | (jq).name \|test("/ST=State of Company") |
|
||||
| (jq).ocs.data | (jq).name \|test("/L=City Name") |
|
||||
| (jq).ocs.data | (jq).name \|test("/O=Organization") |
|
||||
| (jq).ocs.data | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data | (jq).name \|test("/UID=account:signer1") |
|
||||
| (jq).ocs.data | (jq).name \|test("/CN=signer1-displayname") |
|
||||
| (jq).ocs.data.issuer\|length | 6 |
|
||||
|
|
@ -149,7 +149,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.issuer.O | Organization |
|
||||
| (jq).ocs.data.issuer.OU\|length | 2 |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[0] \|test("^Organization Unit$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.issuer.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.subject\|length | 7 |
|
||||
| (jq).ocs.data.subject.CN | signer1-displayname |
|
||||
| (jq).ocs.data.subject.C | BR |
|
||||
|
|
@ -158,7 +158,7 @@ Feature: account/signature
|
|||
| (jq).ocs.data.subject.O | Organization |
|
||||
| (jq).ocs.data.subject.OU \|length | 2 |
|
||||
| (jq).ocs.data.subject.OU | (jq) .[0] \|test("^Organization Unit$") |
|
||||
| (jq).ocs.data.subject.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.subject.OU | (jq) .[1] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.subject.UID | account:signer1 |
|
||||
| (jq).ocs.data.extensions.basicConstraints | CA:FALSE |
|
||||
| (jq).ocs.data.extensions.subjectAltName | email:signer@domain.test |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Feature: admin/certificate_openssl
|
|||
| (jq).ocs.data.rootCert.commonName | Common Name |
|
||||
| (jq).ocs.data.rootCert.names\|length | 1 |
|
||||
| (jq).ocs.data.rootCert.names[0].id | OU |
|
||||
| (jq).ocs.data.rootCert.names[0].value | (jq) .[] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.rootCert.names[0].value | (jq) .[] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.generated | true |
|
||||
|
||||
Scenario: Generate root cert with fail without CN
|
||||
|
|
@ -46,7 +46,7 @@ Feature: admin/certificate_openssl
|
|||
| (jq).ocs.data.rootCert.names[0].id | C |
|
||||
| (jq).ocs.data.rootCert.names[0].value | BR |
|
||||
| (jq).ocs.data.rootCert.names[1].id | OU |
|
||||
| (jq).ocs.data.rootCert.names[1].value | (jq) .[] \|test("^libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.rootCert.names[1].value | (jq) .[] \|test("^libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?$") |
|
||||
| (jq).ocs.data.generated | true |
|
||||
|
||||
Scenario: Generate root cert with fail when country have less then 2 characters
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Feature: validate
|
|||
| (jq).ocs.data.signers[0] | (jq).name \|test("/ST=State of Company") |
|
||||
| (jq).ocs.data.signers[0] | (jq).name \|test("/L=City Name") |
|
||||
| (jq).ocs.data.signers[0] | (jq).name \|test("/O=Organization") |
|
||||
| (jq).ocs.data.signers[0] | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]{10}_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data.signers[0] | (jq).name \|test("/OU=Organization Unit, libresign-ca-id:[a-z0-9]+_g:[0-9]+_e:[oc]?") |
|
||||
| (jq).ocs.data.signers[0] | (jq).name \|test("/CN=signer1-displayname") |
|
||||
| (jq).ocs.data.signers[0].subject.CN | signer1-displayname |
|
||||
| (jq).ocs.data.signers[0].subject.C | BR |
|
||||
|
|
|
|||
|
|
@ -118,3 +118,54 @@ Feature: search
|
|||
| (jq).ocs.data[0].subname | admin@email.tld |
|
||||
| (jq).ocs.data[0].icon | icon-mail |
|
||||
| (jq).ocs.data[0].method | email |
|
||||
|
||||
Scenario: Search account returns acceptsEmailNotifications true when user accepts email
|
||||
Given as user "admin"
|
||||
And user "notification-enabled" exists
|
||||
And set the email of user "notification-enabled" to "enabled@test.com"
|
||||
And run the command "config:app:set activity notify_email_libresign_file_to_sign --value=1" with result code 0
|
||||
And run the command "user:setting notification-enabled activity notify_email_libresign_file_to_sign 1" with result code 0
|
||||
And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods"
|
||||
| value | (string)[{"name":"account","enabled":true}] |
|
||||
When sending "get" to ocs "/apps/libresign/api/v1/identify-account/search?search=notification-enabled"
|
||||
Then the response should have a status code 200
|
||||
And the response should be a JSON array with the following mandatory values
|
||||
| key | value |
|
||||
| (jq).ocs.data\|length | 1 |
|
||||
| (jq).ocs.data[0].id | notification-enabled |
|
||||
| (jq).ocs.data[0].method | account |
|
||||
| (jq).ocs.data[0].acceptsEmailNotifications | true |
|
||||
|
||||
Scenario: Search account returns acceptsEmailNotifications false when user disabled email
|
||||
Given as user "admin"
|
||||
And user "notification-disabled" exists
|
||||
And set the email of user "notification-disabled" to "disabled@test.com"
|
||||
And run the command "config:app:set activity notify_email_libresign_file_to_sign --value=1" with result code 0
|
||||
And run the command "user:setting notification-disabled activity notify_email_libresign_file_to_sign 0" with result code 0
|
||||
And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods"
|
||||
| value | (string)[{"name":"account","enabled":true}] |
|
||||
When sending "get" to ocs "/apps/libresign/api/v1/identify-account/search?search=notification-disabled"
|
||||
Then the response should have a status code 200
|
||||
And the response should be a JSON array with the following mandatory values
|
||||
| key | value |
|
||||
| (jq).ocs.data\|length | 1 |
|
||||
| (jq).ocs.data[0].id | notification-disabled |
|
||||
| (jq).ocs.data[0].method | account |
|
||||
| (jq).ocs.data[0].acceptsEmailNotifications | false |
|
||||
|
||||
Scenario: Search account returns acceptsEmailNotifications false when global setting disabled
|
||||
Given as user "admin"
|
||||
And user "notification-global-off" exists
|
||||
And set the email of user "notification-global-off" to "globaloff@test.com"
|
||||
And run the command "config:app:set activity notify_email_libresign_file_to_sign --value=0" with result code 0
|
||||
And run the command "user:setting notification-global-off activity notify_email_libresign_file_to_sign 1" with result code 0
|
||||
And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods"
|
||||
| value | (string)[{"name":"account","enabled":true}] |
|
||||
When sending "get" to ocs "/apps/libresign/api/v1/identify-account/search?search=notification-global-off"
|
||||
Then the response should have a status code 200
|
||||
And the response should be a JSON array with the following mandatory values
|
||||
| key | value |
|
||||
| (jq).ocs.data\|length | 1 |
|
||||
| (jq).ocs.data[0].id | notification-global-off |
|
||||
| (jq).ocs.data[0].method | account |
|
||||
| (jq).ocs.data[0].acceptsEmailNotifications | false |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
Feature: Custom message for signers
|
||||
In order to provide personalized instructions to signers
|
||||
As a document owner
|
||||
I want to send custom messages to signers via email
|
||||
|
||||
Background:
|
||||
Given as user "admin"
|
||||
And user "signer1" exists
|
||||
And set the email of user "signer1" to "signer1@test.com"
|
||||
And my inbox is empty
|
||||
And reset notifications of user "signer1"
|
||||
And run the command "libresign:configure:openssl --cn test" with result code 0
|
||||
And run the command "config:app:set activity notify_email_libresign_file_to_sign --value=1" with result code 0
|
||||
And run the command "user:setting signer1 activity notify_email_libresign_file_to_sign 1" with result code 0
|
||||
|
||||
Scenario: Account method - default message without custom description
|
||||
When sending "post" to ocs "/apps/libresign/api/v1/request-signature"
|
||||
| file | {"url":"<BASE_URL>/apps/libresign/develop/pdf"} |
|
||||
| name | Document without custom message |
|
||||
| users | [{"identify":{"account":"signer1"}}] |
|
||||
Then the response should have a status code 200
|
||||
And there should be 1 emails in my inbox
|
||||
When I open the latest email to "signer1@test.com" with subject "LibreSign: There is a file for you to sign"
|
||||
Then I should see "There is a document for you to sign" in the opened email
|
||||
|
||||
Scenario: Account method - custom description in email
|
||||
When sending "post" to ocs "/apps/libresign/api/v1/request-signature"
|
||||
| file | {"url":"<BASE_URL>/apps/libresign/develop/pdf"} |
|
||||
| name | Document with custom message |
|
||||
| users | [{"identify":{"account":"signer1"},"description":"Please review section 3 and the appendix before signing."}] |
|
||||
Then the response should have a status code 200
|
||||
And there should be 1 emails in my inbox
|
||||
When I open the latest email to "signer1@test.com" with subject "LibreSign: There is a file for you to sign"
|
||||
Then I should see "Please review section 3 and the appendix before signing" in the opened email
|
||||
And I should see "There is a document for you to sign" in the opened email
|
||||
|
||||
Scenario: Email method - default notification
|
||||
When sending "post" to ocs "/apps/libresign/api/v1/request-signature"
|
||||
| file | {"url":"<BASE_URL>/apps/libresign/develop/pdf"} |
|
||||
| name | Document for email method |
|
||||
| users | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer"}] |
|
||||
Then the response should have a status code 200
|
||||
|
||||
Scenario: Email method - custom description via reminder
|
||||
Given sending "post" to ocs "/apps/libresign/api/v1/request-signature"
|
||||
| file | {"url":"<BASE_URL>/apps/libresign/develop/pdf"} |
|
||||
| name | Document for email with description |
|
||||
| users | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer","description":"Urgent: Please sign by end of day."}] |
|
||||
And the response should have a status code 200
|
||||
And fetch field "(FILE_ID)ocs.data.data.nodeId" from previous JSON response
|
||||
And fetch field "(SIGN_REQUEST_ID)ocs.data.data.signers.0.signRequestId" from previous JSON response
|
||||
And my inbox is empty
|
||||
When sending "post" to ocs "/apps/libresign/api/v1/notify/signer"
|
||||
| fileId | <FILE_ID> |
|
||||
| signRequestId | <SIGN_REQUEST_ID> |
|
||||
Then the response should have a status code 200
|
||||
And there should be 1 emails in my inbox
|
||||
When I open the latest email to "external@domain.test"
|
||||
Then I should see "Urgent: Please sign by end of day" in the opened email
|
||||
Loading…
Add table
Reference in a new issue