mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 18:05:07 +08:00
[feature] Add uploading of PDF signing certificate to Admin Panel
This commit is contained in:
@ -244,6 +244,61 @@ const callCommandService = async body => {
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const getSigningCertificateStatus = async () => {
|
||||
const response = await safeFetch(`${API_BASE_PATH}/config/signing-certificate/status`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) throw new Error('UNAUTHORIZED');
|
||||
throw new Error('Failed to check certificate status');
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const uploadSigningCertificate = async file => {
|
||||
const response = await safeFetch(`${API_BASE_PATH}/config/signing-certificate`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream'
|
||||
},
|
||||
body: file
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) throw new Error('UNAUTHORIZED');
|
||||
if (response.status === 403) throw new Error('Only admin can upload signing certificates');
|
||||
let errorMessage = 'Failed to upload certificate';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch {
|
||||
// JSON parsing failed, use default message
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const deleteSigningCertificate = async () => {
|
||||
const response = await safeFetch(`${API_BASE_PATH}/config/signing-certificate`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) throw new Error('UNAUTHORIZED');
|
||||
if (response.status === 403) throw new Error('Only admin can delete signing certificates');
|
||||
let errorMessage = 'Failed to delete certificate';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch {
|
||||
// JSON parsing failed, use default message
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const getForgottenList = async () => {
|
||||
const result = await callCommandService({c: 'getForgottenList'});
|
||||
const files = result.keys || [];
|
||||
|
||||
@ -1,9 +1,55 @@
|
||||
import {resetConfiguration} from '../../api';
|
||||
import {useState, useEffect, useRef, useCallback} from 'react';
|
||||
import {useSelector, useDispatch} from 'react-redux';
|
||||
import {resetConfiguration, uploadSigningCertificate, deleteSigningCertificate, getSigningCertificateStatus} from '../../api';
|
||||
import {saveConfig, selectConfig} from '../../store/slices/configSlice';
|
||||
import {getNestedValue} from '../../utils/getNestedValue';
|
||||
import {mergeNestedObjects} from '../../utils/mergeNestedObjects';
|
||||
import Button from '../../components/Button/Button';
|
||||
import Input from '../../components/Input/Input';
|
||||
import Section from '../../components/Section/Section';
|
||||
import PasswordInput from '../../components/PasswordInput/PasswordInput';
|
||||
import Note from '../../components/Note/Note';
|
||||
import './Settings.scss';
|
||||
|
||||
const Settings = () => {
|
||||
const dispatch = useDispatch();
|
||||
const config = useSelector(selectConfig);
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
// PDF Signing state
|
||||
const [certificateExists, setCertificateExists] = useState(false);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [signingPassphrase, setSigningPassphrase] = useState('');
|
||||
const [savedPassphrase, setSavedPassphrase] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
const [successMessage, setSuccessMessage] = useState(null);
|
||||
|
||||
// Check certificate status on server
|
||||
const checkCertificateStatus = useCallback(async () => {
|
||||
try {
|
||||
const status = await getSigningCertificateStatus();
|
||||
setCertificateExists(status.exists);
|
||||
} catch (err) {
|
||||
console.error('Failed to check certificate status:', err);
|
||||
setCertificateExists(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load config data and check certificate status
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
const passphrase = getNestedValue(config, 'FileConverter.converter.spawnOptions.env.SIGNING_KEYSTORE_PASSPHRASE') || '';
|
||||
setSigningPassphrase(passphrase);
|
||||
setSavedPassphrase(passphrase);
|
||||
checkCertificateStatus();
|
||||
}
|
||||
}, [config, checkCertificateStatus]);
|
||||
|
||||
const showSuccess = message => {
|
||||
setSuccessMessage(message);
|
||||
setTimeout(() => setSuccessMessage(null), 3000);
|
||||
};
|
||||
|
||||
const handleResetConfig = async () => {
|
||||
if (!window.confirm('Are you sure you want to reset the configuration? This action cannot be undone.')) {
|
||||
throw new Error('Operation cancelled');
|
||||
@ -12,6 +58,130 @@ const Settings = () => {
|
||||
await resetConfiguration();
|
||||
};
|
||||
|
||||
// Handle file selection - stores file in browser state
|
||||
const handleFileSelect = event => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file extension
|
||||
const fileName = file.name.toLowerCase();
|
||||
if (!fileName.endsWith('.p12') && !fileName.endsWith('.pfx')) {
|
||||
setError('Invalid file format. Please select a .p12 or .pfx file.');
|
||||
setSelectedFile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setSelectedFile(file);
|
||||
};
|
||||
|
||||
const handleSelectFileClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
// Save - uploads file (if selected) AND saves/removes passphrase
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
let fileUploaded = false;
|
||||
|
||||
// If file is selected, upload it first
|
||||
if (selectedFile) {
|
||||
await uploadSigningCertificate(selectedFile);
|
||||
fileUploaded = true;
|
||||
}
|
||||
|
||||
// Handle passphrase: empty means remove, non-empty means save
|
||||
const passphraseChanged = signingPassphrase !== savedPassphrase;
|
||||
if (signingPassphrase) {
|
||||
// Save passphrase to config
|
||||
const configUpdate = {
|
||||
'FileConverter.converter.spawnOptions': {
|
||||
env: {
|
||||
SIGNING_KEYSTORE_PASSPHRASE: signingPassphrase
|
||||
}
|
||||
}
|
||||
};
|
||||
const mergedConfig = mergeNestedObjects([configUpdate]);
|
||||
await dispatch(saveConfig(mergedConfig)).unwrap();
|
||||
} else if (passphraseChanged) {
|
||||
// Empty passphrase means remove the key from config (only if changed)
|
||||
await resetConfiguration(['FileConverter.converter.spawnOptions.env.SIGNING_KEYSTORE_PASSPHRASE']);
|
||||
}
|
||||
|
||||
if (fileUploaded) {
|
||||
setCertificateExists(true);
|
||||
}
|
||||
setSavedPassphrase(signingPassphrase);
|
||||
setSelectedFile(null);
|
||||
|
||||
// Reset file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
|
||||
// Show appropriate success message
|
||||
let message;
|
||||
if (fileUploaded && passphraseChanged) {
|
||||
message = signingPassphrase ? 'Certificate uploaded and passphrase saved' : 'Certificate uploaded and passphrase cleared';
|
||||
} else if (fileUploaded) {
|
||||
message = 'Certificate uploaded successfully';
|
||||
} else if (passphraseChanged) {
|
||||
message = signingPassphrase ? 'Passphrase saved successfully' : 'Passphrase cleared successfully';
|
||||
} else {
|
||||
message = 'Settings saved';
|
||||
}
|
||||
showSuccess(message);
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to save');
|
||||
}
|
||||
};
|
||||
|
||||
// Remove - deletes file AND resets passphrase in config
|
||||
const handleRemove = async () => {
|
||||
if (!certificateExists && !selectedFile) return;
|
||||
|
||||
if (!window.confirm('Are you sure you want to remove the signing certificate? This will also clear the passphrase.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
// Delete file from server if exists
|
||||
if (certificateExists) {
|
||||
await deleteSigningCertificate();
|
||||
setCertificateExists(false);
|
||||
}
|
||||
|
||||
// Reset passphrase in config only if it was set
|
||||
if (savedPassphrase) {
|
||||
await resetConfiguration(['FileConverter.converter.spawnOptions.env.SIGNING_KEYSTORE_PASSPHRASE']);
|
||||
}
|
||||
|
||||
setSelectedFile(null);
|
||||
setSigningPassphrase('');
|
||||
setSavedPassphrase('');
|
||||
|
||||
// Reset file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
|
||||
showSuccess('Certificate removed successfully');
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to remove certificate');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePassphraseChange = value => {
|
||||
setSigningPassphrase(value);
|
||||
};
|
||||
|
||||
const hasChanges = selectedFile || signingPassphrase !== savedPassphrase;
|
||||
const canRemove = certificateExists || selectedFile;
|
||||
|
||||
return (
|
||||
<div className='settings-page'>
|
||||
<div className='page-header'>
|
||||
@ -25,6 +195,66 @@ const Settings = () => {
|
||||
>
|
||||
<Button onClick={handleResetConfig}>Reset</Button>
|
||||
</Section>
|
||||
|
||||
<Section title='PDF Digital Signature' description='Configure PKCS#12 (.p12/.pfx) certificate for digitally signing submitted PDF forms'>
|
||||
<Note type='note'>
|
||||
The signing certificate will be used to digitally sign PDF forms when they are submitted. Only submitted PDF forms will be signed, not
|
||||
regular PDF conversions.
|
||||
</Note>
|
||||
|
||||
<div className='form-row'>
|
||||
<div className='certificate-status'>
|
||||
<span className='certificate-label'>Certificate Status:</span>
|
||||
{certificateExists ? (
|
||||
<span className='certificate-installed'>Certificate installed</span>
|
||||
) : (
|
||||
<span className='certificate-not-installed'>No certificate</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-row'>
|
||||
<input ref={fileInputRef} type='file' accept='.p12,.pfx' onChange={handleFileSelect} style={{display: 'none'}} />
|
||||
<div className='file-input-row'>
|
||||
<Input
|
||||
label='Certificate File'
|
||||
value={selectedFile ? selectedFile.name : ''}
|
||||
onChange={() => {}}
|
||||
placeholder='No file selected'
|
||||
readOnly
|
||||
/>
|
||||
<Button onClick={handleSelectFileClick} disableResult>
|
||||
Browse
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-row'>
|
||||
<PasswordInput
|
||||
label='Certificate Passphrase'
|
||||
value={signingPassphrase}
|
||||
onChange={handlePassphraseChange}
|
||||
placeholder='Leave empty if certificate is not encrypted'
|
||||
description='Passphrase to unlock the PKCS#12 certificate. Leave empty if the certificate is not password-protected.'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='form-row'>
|
||||
<div className='actions-section'>
|
||||
<Button onClick={handleSave} disabled={!hasChanges}>
|
||||
Save
|
||||
</Button>
|
||||
{canRemove && (
|
||||
<Button onClick={handleRemove} className='delete-button'>
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className='message-error'>{error}</div>}
|
||||
{successMessage && <div className='message-success'>{successMessage}</div>}
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
.settings-page {
|
||||
margin: 0 auto;
|
||||
padding-bottom: 40px;
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 32px;
|
||||
@ -89,32 +90,91 @@
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
.form-row {
|
||||
margin-bottom: 24px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #721c24;
|
||||
font-size: 14px;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
.certificate-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e0e0e0;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #155724;
|
||||
font-size: 14px;
|
||||
.certificate-label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.certificate-installed {
|
||||
color: #007b14;
|
||||
font-weight: 500;
|
||||
|
||||
&::before {
|
||||
content: '✓ ';
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-not-installed {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.file-input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
|
||||
input[readonly] {
|
||||
cursor: default;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-section {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #f5f5f5 !important;
|
||||
color: #666 !important;
|
||||
border: 1px solid #e0e0e0 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: #e8e8e8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.message-error {
|
||||
color: #cb0000;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #fef2f2;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message-success {
|
||||
color: #007b14;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f0fdf4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
const config = require('config');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const tenantManager = require('../../../../../Common/sources/tenantManager');
|
||||
const runtimeConfigManager = require('../../../../../Common/sources/runtimeConfigManager');
|
||||
const {getScopedConfig, getScopedBaseConfig, validateScoped, getDiffFromBase} = require('./config.service');
|
||||
@ -113,9 +115,9 @@ router.post('/reset', validateJWT, rawFileParser, async (req, res) => {
|
||||
} else {
|
||||
resetConfig = JSON.parse(JSON.stringify(currentConfig));
|
||||
|
||||
paths.forEach(path => {
|
||||
if (path && path !== '*') {
|
||||
const pathParts = path.split('.');
|
||||
paths.forEach(pathItem => {
|
||||
if (pathItem && pathItem !== '*') {
|
||||
const pathParts = pathItem.split('.');
|
||||
let current = resetConfig;
|
||||
|
||||
for (let i = 0; i < pathParts.length - 1; i++) {
|
||||
@ -160,4 +162,112 @@ router.post('/reset', validateJWT, rawFileParser, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get the fixed signing certificate path from config
|
||||
function getSigningCertPath() {
|
||||
return config.get('FileConverter.converter.signingKeyStorePath') || '';
|
||||
}
|
||||
|
||||
// Check signing certificate status (does file exist on disk)
|
||||
router.get('/signing-certificate/status', validateJWT, async (req, res) => {
|
||||
const ctx = req.ctx;
|
||||
try {
|
||||
// Only admin can check certificate status
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
return res.status(403).json({error: 'Only admin can check signing certificate status'});
|
||||
}
|
||||
|
||||
const certPath = getSigningCertPath();
|
||||
|
||||
if (!certPath) {
|
||||
return res.status(200).json({exists: false, configured: false});
|
||||
}
|
||||
|
||||
const fileExists = fs.existsSync(certPath);
|
||||
// Don't expose full path - only return existence status
|
||||
res.status(200).json({exists: fileExists, configured: true});
|
||||
} catch (error) {
|
||||
ctx.logger.error('Signing certificate status check error: %s', error.stack);
|
||||
res.status(500).json({error: 'Failed to check certificate status'});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload signing certificate (.p12/.pfx file) - replaces file at fixed path from config
|
||||
router.post('/signing-certificate', validateJWT, rawFileParser, async (req, res) => {
|
||||
const ctx = req.ctx;
|
||||
try {
|
||||
ctx.logger.info('signing certificate upload start');
|
||||
|
||||
// Only admin can upload certificates
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
return res.status(403).json({error: 'Only admin can upload signing certificates'});
|
||||
}
|
||||
|
||||
if (!req.body || req.body.length === 0) {
|
||||
return res.status(400).json({error: 'No file uploaded'});
|
||||
}
|
||||
|
||||
// Basic validation: P12/PFX files should have reasonable size (1KB - 100KB typically)
|
||||
const MAX_CERT_SIZE = 1024 * 1024; // 1MB max
|
||||
if (req.body.length > MAX_CERT_SIZE) {
|
||||
return res.status(400).json({error: 'File too large. Certificate files should be less than 1MB'});
|
||||
}
|
||||
|
||||
const certPath = getSigningCertPath();
|
||||
if (!certPath) {
|
||||
return res.status(400).json({error: 'signingKeyStorePath is not configured'});
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
const certDir = path.dirname(certPath);
|
||||
if (!fs.existsSync(certDir)) {
|
||||
fs.mkdirSync(certDir, {recursive: true});
|
||||
}
|
||||
|
||||
// Write the file (overwrites existing)
|
||||
fs.writeFileSync(certPath, req.body);
|
||||
|
||||
ctx.logger.info('Signing certificate uploaded successfully: %s', certPath);
|
||||
|
||||
// Don't expose path in response
|
||||
res.status(200).json({success: true});
|
||||
} catch (error) {
|
||||
ctx.logger.error('Signing certificate upload error: %s', error.stack);
|
||||
res.status(500).json({error: 'Failed to upload certificate'});
|
||||
} finally {
|
||||
ctx.logger.info('signing certificate upload end');
|
||||
}
|
||||
});
|
||||
|
||||
// Delete signing certificate - removes file at fixed path from config
|
||||
router.delete('/signing-certificate', validateJWT, async (req, res) => {
|
||||
const ctx = req.ctx;
|
||||
try {
|
||||
ctx.logger.info('signing certificate delete start');
|
||||
|
||||
// Only admin can delete certificates
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
return res.status(403).json({error: 'Only admin can delete signing certificates'});
|
||||
}
|
||||
|
||||
const certPath = getSigningCertPath();
|
||||
|
||||
if (!certPath) {
|
||||
return res.status(404).json({error: 'signingKeyStorePath is not configured'});
|
||||
}
|
||||
|
||||
// Delete the file if it exists
|
||||
if (fs.existsSync(certPath)) {
|
||||
fs.unlinkSync(certPath);
|
||||
ctx.logger.info('Signing certificate deleted: %s', certPath);
|
||||
}
|
||||
|
||||
res.status(200).json({success: true});
|
||||
} catch (error) {
|
||||
ctx.logger.error('Signing certificate delete error: %s', error.stack);
|
||||
res.status(500).json({error: 'Failed to delete certificate'});
|
||||
} finally {
|
||||
ctx.logger.info('signing certificate delete end');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -563,6 +563,7 @@
|
||||
"presentationThemesDir": "null",
|
||||
"x2tPath": "null",
|
||||
"docbuilderPath": "null",
|
||||
"signingKeyStorePath": "",
|
||||
"args": "",
|
||||
"spawnOptions": {},
|
||||
"errorfiles": "",
|
||||
|
||||
@ -80,6 +80,7 @@
|
||||
"presentationThemesDir": "../../sdkjs/slide/themes",
|
||||
"x2tPath": "../FileConverter/bin/x2t",
|
||||
"docbuilderPath": "../FileConverter/bin/docbuilder",
|
||||
"signingKeyStorePath": "./../signing-keystore.p12",
|
||||
"spawnOptions": {
|
||||
"env": {
|
||||
"X2T_MEMORY_LIMIT": "16GB"
|
||||
|
||||
@ -86,6 +86,7 @@
|
||||
"presentationThemesDir": "../../sdkjs/slide/themes",
|
||||
"x2tPath": "../FileConverter/bin/x2t",
|
||||
"docbuilderPath": "../FileConverter/Bin/docbuilder",
|
||||
"signingKeyStorePath": "./../signing-keystore.p12",
|
||||
"spawnOptions": {
|
||||
"env": {
|
||||
"X2T_MEMORY_LIMIT": "16GB"
|
||||
|
||||
@ -86,6 +86,7 @@
|
||||
"presentationThemesDir": "../../sdkjs/slide/themes",
|
||||
"x2tPath": "../FileConverter/Bin/x2t.exe",
|
||||
"docbuilderPath": "../FileConverter/Bin/docbuilder.exe",
|
||||
"signingKeyStorePath": "./../signing-keystore.p12",
|
||||
"spawnOptions": {
|
||||
"env": {
|
||||
"X2T_MEMORY_LIMIT": "16GB"
|
||||
|
||||
@ -71,7 +71,8 @@
|
||||
"fontDir": "/usr/share/fonts",
|
||||
"presentationThemesDir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes",
|
||||
"x2tPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t",
|
||||
"docbuilderPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder"
|
||||
"docbuilderPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder",
|
||||
"signingKeyStorePath": "/var/www/onlyoffice/documentserver/../Data/signing-keystore.p12"
|
||||
}
|
||||
},
|
||||
"SpellChecker": {
|
||||
|
||||
@ -67,7 +67,8 @@
|
||||
"fontDir": "",
|
||||
"presentationThemesDir": "../../sdkjs/slide/themes",
|
||||
"x2tPath": "../FileConverter/bin/x2t.exe",
|
||||
"docbuilderPath": "../FileConverter/bin/docbuilder.exe"
|
||||
"docbuilderPath": "../FileConverter/bin/docbuilder.exe",
|
||||
"signingKeyStorePath": "./../signing-keystore.p12"
|
||||
}
|
||||
},
|
||||
"SpellChecker": {
|
||||
|
||||
@ -217,6 +217,28 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"maxDownloadBytes": {"type": "integer", "minimum": 0, "maximum": 10485760000, "x-scope": ["admin", "tenant"]},
|
||||
"signingKeyStorePath": {
|
||||
"type": "string",
|
||||
"description": "Path to PKCS#12 (.p12/.pfx) keystore file for PDF document signing",
|
||||
"x-scope": "admin"
|
||||
},
|
||||
"spawnOptions": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"x-scope": "admin",
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"SIGNING_KEYSTORE_PASSPHRASE": {
|
||||
"type": "string",
|
||||
"description": "Passphrase for the PKCS#12 keystore file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputLimits": {
|
||||
"type": "array",
|
||||
"x-scope": ["admin", "tenant"],
|
||||
|
||||
@ -69,6 +69,7 @@ const cfgSpawnOptions = config.util.cloneDeep(config.get('FileConverter.converte
|
||||
const cfgErrorFiles = config.get('FileConverter.converter.errorfiles');
|
||||
const cfgInputLimits = config.get('FileConverter.converter.inputLimits');
|
||||
const cfgStreamWriterBufferSize = config.get('FileConverter.converter.streamWriterBufferSize');
|
||||
const cfgSigningKeyStorePath = config.get('FileConverter.converter.signingKeyStorePath');
|
||||
//cfgMaxRequestChanges was obtained as a result of the test: 84408 changes - 5,16 MB
|
||||
const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges');
|
||||
const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles');
|
||||
@ -182,6 +183,12 @@ TaskQueueDataConvert.prototype = {
|
||||
xml += this.serializeXmlProp('m_oTimestamp', this.timestamp.toISOString());
|
||||
xml += this.serializeXmlProp('m_bIsNoBase64', this.noBase64);
|
||||
xml += this.serializeXmlProp('m_sConvertToOrigin', this.convertToOrigin);
|
||||
if (this.formatTo === constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF) {
|
||||
const signingKeyStorePath = ctx.getCfg('FileConverter.converter.signingKeyStorePath', cfgSigningKeyStorePath);
|
||||
if (signingKeyStorePath && fs.existsSync(signingKeyStorePath)) {
|
||||
xml += this.serializeXmlProp('m_sSigningKeyStorePath', signingKeyStorePath);
|
||||
}
|
||||
}
|
||||
xml += this.serializeLimit(ctx);
|
||||
xml += this.serializeOptions(ctx, false, this.oformAsPdf);
|
||||
xml += '</TaskQueueDataConvert>';
|
||||
|
||||
Reference in New Issue
Block a user