From d4564b05fef1c8fb257838c53341efc452055361 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 11 Jan 2024 00:16:43 +0100 Subject: [PATCH] newly created folders will be read-only when needed Close #6296 Signed-off-by: Matthieu Gallien --- src/common/filesystembase.h | 9 +- src/libsync/filesystem.cpp | 135 +++++++++++++++++++++++++- src/libsync/filesystem.h | 14 ++- src/libsync/owncloudpropagator.cpp | 55 ++++++++++- src/libsync/propagatedownload.cpp | 36 ++++++- src/libsync/propagatedownload.h | 5 + src/libsync/propagatorjobs.cpp | 105 ++++++++++++++++++++ test/testpermissions.cpp | 151 +++++++++++++++++++++++++++++ test/testsyncengine.cpp | 7 +- 9 files changed, 501 insertions(+), 16 deletions(-) diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index fd0572804dcb2..6a3baa7afb6e3 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -20,12 +20,13 @@ #include "config.h" +#include "csync/ocsynclib.h" + #include -#include #include #include -#include +#include class QFile; @@ -42,6 +43,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem) * @brief This file contains file system helper */ namespace FileSystem { + enum class FolderPermissions { + ReadOnly, + ReadWrite, + }; /** * @brief Mark the file as hidden (only has effects on windows) diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 7c58c7b2bde45..683d68aa951ab 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -15,15 +15,20 @@ #include "filesystem.h" #include "common/utility.h" +#include "csync.h" +#include "vio/csync_vio_local.h" +#include "std/c_time.h" + #include #include #include #include #include -#include "csync.h" -#include "vio/csync_vio_local.h" -#include "std/c_time.h" +#ifdef Q_OS_WIN +#include +#include +#endif namespace OCC { @@ -189,5 +194,129 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode) return false; } +bool FileSystem::setFolderPermissions(const QString &path, + FileSystem::FolderPermissions permissions) +{ + switch (permissions) { + case OCC::FileSystem::FolderPermissions::ReadOnly: + std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCInfo(lcFileSystem) << "new permissions" << static_cast(std::filesystem::status(path.toStdWString()).permissions()); + break; + case OCC::FileSystem::FolderPermissions::ReadWrite: + break; + } + +#ifdef Q_OS_WIN + SECURITY_INFORMATION info = DACL_SECURITY_INFORMATION; + constexpr auto length = 512; + char securityDescriptor[length]; + auto neededLength = 0ul; + + if (!GetFileSecurityW(path.toStdWString().c_str(), info, &securityDescriptor, length, &neededLength)) { + if (neededLength > length) { + qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << "size is too small to get the result" << "need" << neededLength << "instead of" << length; + return false; + } + qCWarning(lcFileSystem) << "error when calling GetFileSecurityW" << path << GetLastError(); + return false; + } + + int daclPresent = false, daclDefault = false; + PACL resultDacl = nullptr; + if (!GetSecurityDescriptorDacl(&securityDescriptor, &daclPresent, &resultDacl, &daclDefault)) { + qCWarning(lcFileSystem) << "error when calling GetSecurityDescriptorDacl" << path << GetLastError(); + return false; + } + if (!daclPresent) { + qCWarning(lcFileSystem) << "error when calling DACL needed to set a folder read-only or read-write is missing" << path; + return false; + } + + PACL newDacl = reinterpret_cast(new char[length]); + if (!InitializeAcl(newDacl, length, ACL_REVISION)) { + qCWarning(lcFileSystem) << "error when calling InitializeAcl" << path << GetLastError(); + return false; + } + + PSID sid = nullptr; + if (!ConvertStringSidToSidA("S-1-5-32-545", &sid)) + { + qCWarning(lcFileSystem) << "error when calling ConvertStringSidToSidA" << path << GetLastError(); + return false; + } + + if (permissions == FileSystem::FolderPermissions::ReadOnly) { + qCInfo(lcFileSystem) << path << "will be read only"; + if (!AddAccessDeniedAce(newDacl, ACL_REVISION, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD, sid)) { + qCWarning(lcFileSystem) << "error when calling AddAccessDeniedAce << path" << GetLastError(); + return false; + } + } + + ACL_SIZE_INFORMATION aclSize; + if (!GetAclInformation(resultDacl, &aclSize, sizeof(aclSize), AclSizeInformation)) { + qCWarning(lcFileSystem) << "error when calling GetAclInformation" << path << GetLastError(); + return false; + } + + for (int i = 0; i < aclSize.AceCount; ++i) { + void *currentAce = nullptr; + if (!GetAce(resultDacl, i, ¤tAce)) { + qCWarning(lcFileSystem) << "error when calling GetAce" << path << GetLastError(); + return false; + } + + const auto currentAceHeader = reinterpret_cast(currentAce); + + if (permissions == FileSystem::FolderPermissions::ReadWrite) { + qCInfo(lcFileSystem) << path << "will be read write"; + } + if (permissions == FileSystem::FolderPermissions::ReadWrite && (ACCESS_DENIED_ACE_TYPE == (currentAceHeader->AceType & ACCESS_DENIED_ACE_TYPE))) { + qCWarning(lcFileSystem) << "AceHeader" << path << currentAceHeader->AceFlags << currentAceHeader->AceSize << currentAceHeader->AceType; + continue; + } + + if (!AddAce(newDacl, ACL_REVISION, i + 1, currentAce, currentAceHeader->AceSize)) { + qCWarning(lcFileSystem) << "error when calling AddAce" << path << GetLastError(); + return false; + } + } + + SECURITY_DESCRIPTOR newSecurityDescriptor; + if (!InitializeSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) { + qCWarning(lcFileSystem) << "error when calling InitializeSecurityDescriptor" << path << GetLastError(); + return false; + } + + if (!SetSecurityDescriptorDacl(&newSecurityDescriptor, true, newDacl, false)) { + qCWarning(lcFileSystem) << "error when calling SetSecurityDescriptorDacl" << path << GetLastError(); + return false; + } + + if (!SetFileSecurityW(path.toStdWString().c_str(), info, &newSecurityDescriptor)) { + qCWarning(lcFileSystem) << "error when calling SetFileSecurityW" << path << GetLastError(); + return false; + } +#endif + + switch (permissions) { + case OCC::FileSystem::FolderPermissions::ReadOnly: + break; + case OCC::FileSystem::FolderPermissions::ReadWrite: + std::filesystem::permissions(path.toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCInfo(lcFileSystem) << "new permissions" << static_cast(std::filesystem::status(path.toStdWString()).permissions()); + break; + } + + return true; +} + +bool FileSystem::isFolderReadOnly(const std::filesystem::path &path) +{ + const auto folderStatus = std::filesystem::status(path); + const auto folderPermissions = folderStatus.permissions(); + return (folderPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write; +} + } // namespace OCC diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 602f74bb7f2bc..4d3a81a0d2629 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -16,13 +16,14 @@ #include "config.h" +#include "owncloudlib.h" +#include "common/filesystembase.h" + #include + #include #include - -#include -// Chain in the base include and extend the namespace -#include "common/filesystembase.h" +#include class QFile; @@ -96,6 +97,11 @@ namespace FileSystem { bool OWNCLOUDSYNC_EXPORT removeRecursively(const QString &path, const std::function &onDeleted = nullptr, QStringList *errors = nullptr); + + bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path, + FileSystem::FolderPermissions permissions); + + bool OWNCLOUDSYNC_EXPORT isFolderReadOnly(const std::filesystem::path &path); } /** @} */ diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index a5d10ed1c9037..620f73c6176c8 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -1442,6 +1442,59 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) if (_item->_instruction == CSYNC_INSTRUCTION_RENAME || _item->_instruction == CSYNC_INSTRUCTION_NEW || _item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) { + + if (!_item->_remotePerm.isNull() && + !_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanRename) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) { + try { + if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadOnly); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + } + if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadOnly); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write | std::filesystem::perms::group_write | std::filesystem::perms::others_write, std::filesystem::perm_options::remove); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + _item->_status = SyncFileItem::NormalError; + _item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(_item->_file, e.what()); + } + } else if (!_item->_remotePerm.isNull() && + (_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) || + !_item->_remotePerm.hasPermission(RemotePermissions::CanRename) || + !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) || + !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) { + try { + if (QFileInfo::exists(propagator()->fullLocalPath(_item->_file))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_file), FileSystem::FolderPermissions::ReadWrite); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_file).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_file).toStdWString()).permissions()); + } + if (!_item->_renameTarget.isEmpty() && QFileInfo::exists(propagator()->fullLocalPath(_item->_renameTarget))) { + FileSystem::setFolderPermissions(propagator()->fullLocalPath(_item->_renameTarget), FileSystem::FolderPermissions::ReadWrite); + qCDebug(lcDirectory) << "old permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + std::filesystem::permissions(propagator()->fullLocalPath(_item->_renameTarget).toStdWString(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add); + qCDebug(lcDirectory) << "new permissions" << static_cast(std::filesystem::status(propagator()->fullLocalPath(_item->_renameTarget).toStdWString()).permissions()); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcDirectory) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + _item->_status = SyncFileItem::NormalError; + _item->_errorString = tr("The folder %1 cannot be made read-only: %2").arg(e.path1().c_str(), e.what()); + } + } + const auto result = propagator()->updateMetadata(*_item); if (!result) { status = _item->_status = SyncFileItem::FatalError; @@ -1454,7 +1507,7 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status) } } _state = Finished; - qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status; + qCInfo(lcPropagator) << "PropagateDirectory::slotSubJobsFinished" << "emit finished" << status << _item->_file; emit finished(status); } diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 11b9935a4e6ca..d724a4565521c 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -32,11 +32,8 @@ #include #include #include -#include -#ifdef Q_OS_UNIX -#include -#endif +#include namespace OCC { @@ -672,8 +669,25 @@ void PropagateDownloadFile::startDownload() // Can't open(Append) read-only files, make sure to make // file writable if it exists. - if (_tmpFile.exists()) + if (_tmpFile.exists()) { FileSystem::setFileReadOnly(_tmpFile.fileName(), false); + } + + try { + const auto newDirPath = std::filesystem::path{_tmpFile.fileName().toStdWString()}; + Q_ASSERT(newDirPath.has_parent_path()); + _parentPath = newDirPath.parent_path(); + if (FileSystem::isFolderReadOnly(_parentPath)) { + FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite); + emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring())); + _needParentFolderRestorePermissions = true; + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateDownload) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) { qCWarning(lcPropagateDownload) << "could not open temporary file" << _tmpFile.fileName(); done(SyncFileItem::NormalError, _tmpFile.errorString(), ErrorCategory::GenericError); @@ -1272,6 +1286,18 @@ void PropagateDownloadFile::downloadFinished() return; } + if (_needParentFolderRestorePermissions) { + try { + FileSystem::setFolderPermissions(QString::fromStdWString(_parentPath.wstring()), FileSystem::FolderPermissions::ReadWrite); + emit propagator()->touchedFile(QString::fromStdWString(_parentPath.wstring())); + _needParentFolderRestorePermissions = false; + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateDownload) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + } + FileSystem::setFileHidden(filename, false); // Maybe we downloaded a newer version of the file than we thought we would... diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index a56cdd4a1b0d4..23c72f0aebaea 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -23,6 +23,8 @@ #include #include +#include + namespace OCC { class PropagateDownloadEncrypted; @@ -260,5 +262,8 @@ private slots: QElapsedTimer _stopwatch; PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr; + + std::filesystem::path _parentPath; + bool _needParentFolderRestorePermissions = false; }; } diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 4cc6e495b08b6..e016fd0652d84 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -32,6 +32,7 @@ #include #include +#include #include @@ -181,6 +182,24 @@ void PropagateLocalMkdir::startLocalMkdir() done(SyncFileItem::FileNameClash, tr("Folder %1 cannot be created because of a local file or folder name clash!").arg(newDirStr), ErrorCategory::GenericError); return; } + + auto parentFolderPath = std::filesystem::path{}; + auto parentNeedRollbackPermissions = false; + try { + const auto newDirPath = std::filesystem::path{newDirStr.toStdWString()}; + Q_ASSERT(newDirPath.has_parent_path()); + parentFolderPath = newDirPath.parent_path(); + if (FileSystem::isFolderReadOnly(parentFolderPath)) { + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite); + parentNeedRollbackPermissions = true; + emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + emit propagator()->touchedFile(newDirStr); QDir localDir(propagator()->localPath()); if (!localDir.mkpath(_item->_file)) { @@ -188,6 +207,33 @@ void PropagateLocalMkdir::startLocalMkdir() return; } + if (!_item->_remotePerm.isNull() && + !_item->_remotePerm.hasPermission(RemotePermissions::CanAddFile) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanRename) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanMove) && + !_item->_remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)) { + try { + FileSystem::setFolderPermissions(newDirStr, FileSystem::FolderPermissions::ReadOnly); + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + done(SyncFileItem::NormalError, tr("The folder %1 cannot be made read-only: %2").arg(_item->_file, e.what()), ErrorCategory::GenericError); + return; + } + } + + try { + if (parentNeedRollbackPermissions) { + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadOnly); + emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalMkdir) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + // Insert the directory into the database. The correct etag will be set later, // once all contents have been propagated, because should_update_metadata is true. // Adding an entry with a dummy etag to the database still makes sense here @@ -257,12 +303,71 @@ void PropagateLocalRename::start() return; } + auto targetParentFolderPath = std::filesystem::path{}; + auto targetParentFolderWasReadOnly = false; + try { + const auto newDirPath = std::filesystem::path{targetFile.toStdWString()}; + Q_ASSERT(newDirPath.has_parent_path()); + targetParentFolderPath = newDirPath.parent_path(); + if (FileSystem::isFolderReadOnly(targetParentFolderPath)) { + targetParentFolderWasReadOnly = true; + FileSystem::setFolderPermissions(QString::fromStdWString(targetParentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite); + emit propagator()->touchedFile(QString::fromStdWString(targetParentFolderPath.wstring())); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + + auto originParentFolderPath = std::filesystem::path{}; + auto originParentFolderWasReadOnly = false; + try { + const auto newDirPath = std::filesystem::path{existingFile.toStdWString()}; + Q_ASSERT(newDirPath.has_parent_path()); + originParentFolderPath = newDirPath.parent_path(); + if (FileSystem::isFolderReadOnly(originParentFolderPath)) { + originParentFolderWasReadOnly = true; + FileSystem::setFolderPermissions(QString::fromStdWString(originParentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite); + emit propagator()->touchedFile(QString::fromStdWString(originParentFolderPath.wstring())); + } + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + + const auto restoreTargetPermissions = [this] (const auto &parentFolderPath) { + try { + FileSystem::setFolderPermissions(QString::fromStdWString(parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadOnly); + emit propagator()->touchedFile(QString::fromStdWString(parentFolderPath.wstring())); + } + catch (const std::filesystem::filesystem_error &e) + { + qCWarning(lcPropagateLocalRename) << "exception when checking parent folder access rights" << e.what() << e.path1().c_str() << e.path2().c_str(); + } + }; + emit propagator()->touchedFile(existingFile); emit propagator()->touchedFile(targetFile); if (QString renameError; !FileSystem::rename(existingFile, targetFile, &renameError)) { + if (targetParentFolderWasReadOnly) { + restoreTargetPermissions(targetParentFolderPath); + } + if (originParentFolderWasReadOnly) { + restoreTargetPermissions(originParentFolderPath); + } + done(SyncFileItem::NormalError, renameError, ErrorCategory::GenericError); return; } + + if (targetParentFolderWasReadOnly) { + restoreTargetPermissions(targetParentFolderPath); + } + if (originParentFolderWasReadOnly) { + restoreTargetPermissions(originParentFolderPath); + } } SyncJournalFileRecord oldRecord; diff --git a/test/testpermissions.cpp b/test/testpermissions.cpp index cdcf9049a6bc1..cda94d407dffd 100644 --- a/test/testpermissions.cpp +++ b/test/testpermissions.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace OCC; @@ -590,6 +591,156 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } + + static void demo_perms(std::filesystem::perms p) + { + using std::filesystem::perms; + auto show = [=](char op, perms perm) + { + std::cout << (perms::none == (perm & p) ? '-' : op); + }; + show('r', perms::owner_read); + show('w', perms::owner_write); + show('x', perms::owner_exec); + show('r', perms::group_read); + show('w', perms::group_write); + show('x', perms::group_exec); + show('r', perms::others_read); + show('w', perms::others_write); + show('x', perms::others_exec); + std::cout << std::endl; + } + + void testReadOnlyFolderIsReallyReadOnly() + { + FakeFolder fakeFolder{FileInfo{}}; + + auto &remote = fakeFolder.remoteModifier(); + + remote.mkdir("readOnlyFolder"); + + remote.find("readOnlyFolder")->permissions = RemotePermissions::fromServerString("M"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const auto folderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/readOnlyFolder")).toStdWString()); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read); + } + + void testReadWriteFolderIsReallyReadWrite() + { + FakeFolder fakeFolder{FileInfo{}}; + + auto &remote = fakeFolder.remoteModifier(); + + remote.mkdir("readWriteFolder"); + + remote.find("readWriteFolder")->permissions = RemotePermissions::fromServerString("WDNVRSM"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const auto folderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/readWriteFolder")).toStdWString()); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_write); + } + + void testChangePermissionsFolder() + { + FakeFolder fakeFolder{FileInfo{}}; + + auto &remote = fakeFolder.remoteModifier(); + + remote.mkdir("testFolder"); + + remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + auto folderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString()); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(folderStatus.permissions() & std::filesystem::perms::owner_write)); + + remote.find("testFolder")->permissions = RemotePermissions::fromServerString("WDNVRSM"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + folderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString()); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_write); + + remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + folderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString()); + QVERIFY(folderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(folderStatus.permissions() & std::filesystem::perms::owner_write)); + } + + void testChangePermissionsForFolderHierarchy() + { + FakeFolder fakeFolder{FileInfo{}}; + + auto &remote = fakeFolder.remoteModifier(); + + remote.mkdir("testFolder"); + + remote.find("testFolder")->permissions = RemotePermissions::fromServerString("M"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + remote.mkdir("testFolder/subFolderReadWrite"); + remote.mkdir("testFolder/subFolderReadOnly"); + + remote.find("testFolder/subFolderReadOnly")->permissions = RemotePermissions::fromServerString("m"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + auto testFolderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString()); + QVERIFY(testFolderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(testFolderStatus.permissions() & std::filesystem::perms::owner_write)); + auto subFolderReadWriteStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadWrite")).toStdWString()); + QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_write); + auto subFolderReadOnlyStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadOnly")).toStdWString()); + QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_write)); + + remote.find("testFolder/subFolderReadOnly")->permissions = RemotePermissions::fromServerString("WDNVRSm"); + remote.find("testFolder/subFolderReadWrite")->permissions = RemotePermissions::fromServerString("m"); + remote.mkdir("testFolder/newSubFolder"); + remote.create("testFolder/testFile", 12, '9'); + remote.create("testFolder/testReadOnlyFile", 13, '8'); + remote.find("testFolder/testReadOnlyFile")->permissions = RemotePermissions::fromServerString("m"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + subFolderReadWriteStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadWrite")).toStdWString()); + QVERIFY(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(subFolderReadWriteStatus.permissions() & std::filesystem::perms::owner_write)); + subFolderReadOnlyStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder/subFolderReadOnly")).toStdWString()); + QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(subFolderReadOnlyStatus.permissions() & std::filesystem::perms::owner_write); + + remote.rename("testFolder/subFolderReadOnly", "testFolder/subFolderReadWriteNew"); + remote.rename("testFolder/subFolderReadWrite", "testFolder/subFolderReadOnlyNew"); + remote.rename("testFolder/testFile", "testFolder/testFileNew"); + remote.rename("testFolder/testReadOnlyFile", "testFolder/testReadOnlyFileNew"); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + testFolderStatus = std::filesystem::status(static_cast(fakeFolder.localPath() + QStringLiteral("/testFolder")).toStdWString()); + QVERIFY(testFolderStatus.permissions() & std::filesystem::perms::owner_read); + QVERIFY(!static_cast(testFolderStatus.permissions() & std::filesystem::perms::owner_write)); + } }; QTEST_GUILESS_MAIN(TestPermissions) diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 15ac965f166f2..82a3c23906775 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -95,6 +95,11 @@ private slots: Logger::instance()->setLogDebug(true); } + void init() + { + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); + } + void testFileDownload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; ItemCompletedSpy completeSpy(fakeFolder); @@ -845,12 +850,12 @@ private slots: QVERIFY(fakeFolder.syncOnce()); QVERIFY(fakeFolder.currentRemoteState().find("C/tößt")); - QTextCodec::setCodecForLocale(utf8Locale); } catch (const std::filesystem::filesystem_error &e) { qCritical() << e.what() << e.path1().c_str() << e.path2().c_str() << e.code().message().c_str(); } + QTextCodec::setCodecForLocale(utf8Locale); #endif }