fix(encryption): Correctly set encrypted to 0 when copying

If encryption got disabled, copying should set encrypted to 0 for the
 new unencrypted copy. For instance when using encryption:decrypt-all

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
Louis Chemineau 2025-09-04 12:14:04 +02:00 committed by nextcloud-command
parent 186e725910
commit 6d672c4cba
4 changed files with 92 additions and 44 deletions

View file

@ -9,6 +9,7 @@ import type { TemplateFile } from '../types.ts'
import { Folder, Node, Permission, addNewFileMenuEntry } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { isPublicShare } from '@nextcloud/sharing/public'
import { newNodeName } from '../utils/newNodeDialog'
import { translate as t } from '@nextcloud/l10n'
import Vue, { defineAsyncComponent } from 'vue'
@ -46,7 +47,12 @@ const getTemplatePicker = async (context: Folder) => {
* Register all new-file-menu entries for all template providers
*/
export function registerTemplateEntries() {
const templates = loadState<TemplateFile[]>('files', 'templates', [])
let templates: TemplateFile[]
if (isPublicShare()) {
templates = loadState<TemplateFile[]>('files_sharing', 'templates', [])
} else {
templates = loadState<TemplateFile[]>('files', 'templates', [])
}
// Init template files menu
templates.forEach((provider, index) => {

View file

@ -52,7 +52,8 @@ import type { TemplateFile } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showError, spawnDialog } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { File } from '@nextcloud/files'
import { File, Node } from '@nextcloud/files'
import { getClient, getRootPath, resultToNode, getDefaultPropfind } from '@nextcloud/files/dav'
import { translate as t } from '@nextcloud/l10n'
import { generateRemoteUrl } from '@nextcloud/router'
import { normalize, extname, join } from 'path'
@ -64,6 +65,7 @@ import NcModal from '@nextcloud/vue/components/NcModal'
import TemplatePreview from '../components/TemplatePreview.vue'
import TemplateFiller from '../components/TemplateFiller.vue'
import logger from '../logger.ts'
import type { FileStat, ResponseDataDetailed } from 'webdav'
const border = 2
const margin = 8
@ -167,6 +169,12 @@ export default defineComponent({
this.name = name
this.provider = provider
// Skip templates logic for external users.
if (getCurrentUser() === null) {
this.onSubmit()
return
}
const templates = await getTemplates()
const fetchedProvider = templates.find((fetchedProvider) => fetchedProvider.app === provider.app && fetchedProvider.label === provider.label)
if (fetchedProvider === null) {
@ -224,56 +232,80 @@ export default defineComponent({
this.name = `${this.name}${this.provider?.extension ?? ''}`
}
try {
const fileInfo = await createFromTemplate(
normalize(`${currentDirectory}/${this.name}`),
this.selectedTemplate?.filename as string ?? '',
this.selectedTemplate?.templateType as string ?? '',
templateFields,
)
logger.debug('Created new file', fileInfo)
// Create a blank file for external users as we can't use the templates.
if (getCurrentUser() === null) {
const client = getClient()
const filename = join(getRootPath(), currentDirectory, this.name ?? '')
const owner = getCurrentUser()?.uid || null
const node = new File({
id: fileInfo.fileid,
source: generateRemoteUrl(join(`dav/files/${owner}`, fileInfo.filename)),
root: `/files/${owner}`,
mime: fileInfo.mime,
mtime: new Date(fileInfo.lastmod * 1000),
owner,
size: fileInfo.size,
permissions: fileInfo.permissions,
attributes: {
// Inherit some attributes from parent folder like the mount type and real owner
'mount-type': this.parent?.attributes?.['mount-type'],
'owner-id': this.parent?.attributes?.['owner-id'],
'owner-display-name': this.parent?.attributes?.['owner-display-name'],
...fileInfo,
'has-preview': fileInfo.hasPreview,
},
})
await client.putFileContents(filename, '')
const response = await client.stat(filename, { data: getDefaultPropfind(), details: true }) as ResponseDataDetailed<FileStat>
logger.debug('Created new file', { fileInfo: response.data })
// Update files list
emit('files:node:created', node)
const node = resultToNode(response.data)
// Open the new file
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir: node.dirname, openfile: 'true' },
)
this.handleFileCreation(node)
} else {
try {
const fileInfo = await createFromTemplate(
normalize(`${currentDirectory}/${this.name}`),
this.selectedTemplate?.filename as string ?? '',
this.selectedTemplate?.templateType as string ?? '',
templateFields,
)
logger.debug('Created new file', { fileInfo })
// Close the picker
this.close()
} catch (error) {
logger.error('Error while creating the new file from template', { error })
showError(t('files', 'Unable to create new file from template'))
} finally {
this.loading = false
const owner = getCurrentUser()?.uid || null
const node = new File({
id: fileInfo.fileid,
source: generateRemoteUrl(join(`dav/files/${owner}`, fileInfo.filename)),
root: `/files/${owner}`,
mime: fileInfo.mime,
mtime: new Date(fileInfo.lastmod * 1000),
owner,
size: fileInfo.size,
permissions: fileInfo.permissions,
attributes: {
// Inherit some attributes from parent folder like the mount type and real owner
'mount-type': this.parent?.attributes?.['mount-type'],
'owner-id': this.parent?.attributes?.['owner-id'],
'owner-display-name': this.parent?.attributes?.['owner-display-name'],
...fileInfo,
'has-preview': fileInfo.hasPreview,
},
})
this.handleFileCreation(node)
// Close the picker
this.close()
} catch (error) {
logger.error('Error while creating the new file from template', { error })
showError(t('files', 'Unable to create new file from template'))
} finally {
this.loading = false
}
}
},
handleFileCreation(node: Node) {
// Update files list
emit('files:node:created', node)
// Open the new file
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir: node.dirname, openfile: 'true' },
)
},
async onSubmit() {
// Skip templates logic for external users.
if (getCurrentUser() === null) {
this.loading = true
return this.createFile()
}
const fileId = this.selectedTemplate?.fileid
// Only request field extraction if there is a valid template

View file

@ -24,6 +24,7 @@ use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\Template\ITemplateManager;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
@ -49,6 +50,7 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
private Defaults $defaults,
private IConfig $config,
private IRequest $request,
private ITemplateManager $templateManager,
private IInitialState $initialState,
private IAppConfig $appConfig,
) {
@ -119,6 +121,8 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}
$this->initialState->provideInitialState('templates', $this->templateManager->listCreators());
// Allow external apps to register their scripts
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));

View file

@ -31,6 +31,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Template\ITemplateManager;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
@ -68,6 +69,7 @@ class ShareControllerTest extends \Test\TestCase {
private Manager&MockObject $shareManager;
private IPreview&MockObject $previewManager;
private IUserManager&MockObject $userManager;
private ITemplateManager&MockObject $templateManager;
private IInitialState&MockObject $initialState;
private IURLGenerator&MockObject $urlGenerator;
private ISecureRandom&MockObject $secureRandom;
@ -87,6 +89,7 @@ class ShareControllerTest extends \Test\TestCase {
$this->config = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->templateManager = $this->createMock(ITemplateManager::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
$this->federatedShareProvider->expects($this->any())
@ -114,6 +117,7 @@ class ShareControllerTest extends \Test\TestCase {
$this->defaults,
$this->config,
$this->createMock(IRequest::class),
$this->templateManager,
$this->initialState,
$this->appConfig,
)
@ -338,6 +342,7 @@ class ShareControllerTest extends \Test\TestCase {
'owner' => 'ownerUID',
'ownerDisplayName' => 'ownerDisplay',
'isFileRequest' => false,
'templates' => [],
];
$response = $this->shareController->showShare();
@ -485,6 +490,7 @@ class ShareControllerTest extends \Test\TestCase {
'isFileRequest' => false,
'note' => 'The note',
'label' => 'A label',
'templates' => [],
];
$response = $this->shareController->showShare();