Skip to content

Commit

Permalink
newly created folders will be read-only when needed
Browse files Browse the repository at this point in the history
Close #6296

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
  • Loading branch information
mgallien committed Mar 7, 2024
1 parent ec56786 commit 64e2b99
Show file tree
Hide file tree
Showing 9 changed files with 493 additions and 16 deletions.
9 changes: 7 additions & 2 deletions src/common/filesystembase.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@

#include "config.h"

Check failure on line 21 in src/common/filesystembase.h

View workflow job for this annotation

GitHub Actions / build

src/common/filesystembase.h:21:10 [clang-diagnostic-error]

'config.h' file not found

#include "csync/ocsynclib.h"

#include <QString>
#include <ctime>
#include <QFileInfo>
#include <QLoggingCategory>

#include <csync/ocsynclib.h>
#include <ctime>

class QFile;

Expand All @@ -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)
Expand Down
130 changes: 127 additions & 3 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
#include "filesystem.h"

#include "common/utility.h"
#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_time.h"

#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCoreApplication>

#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_time.h"
#include <filesystem>

#ifdef Q_OS_WIN
#include <securitybaseapi.h>
#include <sddl.h>
#endif

namespace OCC {

Expand Down Expand Up @@ -189,5 +196,122 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode)
return false;
}

bool FileSystem::setFolderPermissions(const QString &path,

Check warning on line 199 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:199:18 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 199 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:199:39 [bugprone-easily-swappable-parameters]

2 adjacent parameters of 'setFolderPermissions' of similar type are easily swapped by mistake
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<int>(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<PACL>(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, &currentAce)) {
qCWarning(lcFileSystem) << "error when calling GetAce" << path << GetLastError();
return false;
}

const auto currentAceHeader = reinterpret_cast<PACE_HEADER>(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<int>(std::filesystem::status(path.toStdWString()).permissions());
break;
}

return true;
}


} // namespace OCC
11 changes: 7 additions & 4 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

#include "config.h"

Check failure on line 17 in src/libsync/filesystem.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.h:17:10 [clang-diagnostic-error]

'config.h' file not found

#include "owncloudlib.h"
#include "common/filesystembase.h"

#include <QString>

#include <ctime>
#include <functional>

#include <owncloudlib.h>
// Chain in the base include and extend the namespace
#include "common/filesystembase.h"

class QFile;

Check warning on line 27 in src/libsync/filesystem.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.h:27:7 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'QFile' is non-const and globally accessible, consider making it const

namespace OCC {
Expand Down Expand Up @@ -96,6 +96,9 @@ namespace FileSystem {
bool OWNCLOUDSYNC_EXPORT removeRecursively(const QString &path,
const std::function<void(const QString &path, bool isDir)> &onDeleted = nullptr,
QStringList *errors = nullptr);

bool OWNCLOUDSYNC_EXPORT setFolderPermissions(const QString &path,
FileSystem::FolderPermissions permissions);
}

/** @} */
Expand Down
55 changes: 54 additions & 1 deletion src/libsync/owncloudpropagator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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;
Expand All @@ -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);
}

Expand Down
38 changes: 33 additions & 5 deletions src/libsync/propagatedownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>

#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
#include <cmath>

namespace OCC {

Expand Down Expand Up @@ -672,8 +669,27 @@ 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());
_parentFolderPath = newDirPath.parent_path();
auto parentFolderStatus = std::filesystem::status(_parentFolderPath);
_parentPermissions = parentFolderStatus.permissions();
if ((_parentPermissions & std::filesystem::perms::owner_write) != std::filesystem::perms::owner_write) {
FileSystem::setFolderPermissions(QString::fromStdWString(_parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
emit propagator()->touchedFile(QString::fromStdWString(_parentFolderPath.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);
Expand Down Expand Up @@ -1272,6 +1288,18 @@ void PropagateDownloadFile::downloadFinished()
return;
}

if (_needParentFolderRestorePermissions) {
try {
FileSystem::setFolderPermissions(QString::fromStdWString(_parentFolderPath.wstring()), FileSystem::FolderPermissions::ReadWrite);
emit propagator()->touchedFile(QString::fromStdWString(_parentFolderPath.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...
Expand Down
6 changes: 6 additions & 0 deletions src/libsync/propagatedownload.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <QBuffer>
#include <QFile>

#include <filesystem>

namespace OCC {
class PropagateDownloadEncrypted;

Expand Down Expand Up @@ -260,5 +262,9 @@ private slots:
QElapsedTimer _stopwatch;

PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr;

std::filesystem::perms _parentPermissions;
std::filesystem::path _parentFolderPath;
bool _needParentFolderRestorePermissions = false;
};
}
Loading

0 comments on commit 64e2b99

Please sign in to comment.