From aa55275cc8cf2254417f3dc45d0f34489ceb3bbb Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 21 Aug 2023 11:31:56 +0200 Subject: [PATCH 1/2] add an automated test to check that locked files are read-only Signed-off-by: Matthieu Gallien --- test/testlockfile.cpp | 39 +++++++++++++++++++++++++++++++++++ test/testsynccfapi.cpp | 39 +++++++++++++++++++++++++++++++++++ test/testsyncvirtualfiles.cpp | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/test/testlockfile.cpp b/test/testlockfile.cpp index d0dcc2ae523f8..c95927cdf9426 100644 --- a/test/testlockfile.cpp +++ b/test/testlockfile.cpp @@ -631,6 +631,45 @@ private slots: auto expectedState = fakeFolder.currentLocalState(); QCOMPARE(fakeFolder.currentRemoteState(), expectedState); } + + void testLockFile_lockedFileReadOnly_afterSync() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + ItemCompletedSpy completeSpy(fakeFolder); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncJournalFileRecord fileRecordBefore; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore)); + QVERIFY(!fileRecordBefore._lockstate._locked); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const auto localFileNotLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"}; + QVERIFY(localFileNotLocked.isWritable()); + + fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226); + fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime()); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncJournalFileRecord fileRecordLocked; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked)); + QVERIFY(fileRecordLocked._lockstate._locked); + + auto expectedState = fakeFolder.currentLocalState(); + QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + + const auto localFileLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"}; + QVERIFY(!localFileLocked.isWritable()); + } }; QTEST_GUILESS_MAIN(TestLockFile) diff --git a/test/testsynccfapi.cpp b/test/testsynccfapi.cpp index cc1a023f5bcdf..fc6602dcd77d5 100644 --- a/test/testsynccfapi.cpp +++ b/test/testsynccfapi.cpp @@ -1315,6 +1315,45 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } + + void testLockFile_lockedFileReadOnly_afterSync() + { + FakeFolder fakeFolder{ FileInfo{} }; + setupVfs(fakeFolder); + + ItemCompletedSpy completeSpy(fakeFolder); + + fakeFolder.remoteModifier().mkdir("A"); + fakeFolder.remoteModifier().insert("A/a1"); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncJournalFileRecord fileRecordBefore; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore)); + QVERIFY(fileRecordBefore.isValid()); + QVERIFY(!fileRecordBefore._lockstate._locked); + + const auto localFileNotLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"}; + QVERIFY(localFileNotLocked.isWritable()); + + fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226); + fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime()); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncJournalFileRecord fileRecordLocked; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked)); + QVERIFY(fileRecordLocked.isValid()); + QVERIFY(fileRecordLocked._lockstate._locked); + + const auto localFileLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"}; + QVERIFY(!localFileLocked.isWritable()); + } }; QTEST_GUILESS_MAIN(TestSyncCfApi) diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp index a4ce8c8c66ce6..26e8ffce90a54 100644 --- a/test/testsyncvirtualfiles.cpp +++ b/test/testsyncvirtualfiles.cpp @@ -1906,6 +1906,45 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } + + void testLockFile_lockedFileReadOnly_afterSync() + { + FakeFolder fakeFolder{ FileInfo{} }; + setupVfs(fakeFolder); + + ItemCompletedSpy completeSpy(fakeFolder); + + fakeFolder.remoteModifier().mkdir("A"); + fakeFolder.remoteModifier().insert("A/a1"); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1") + DVSUFFIX)->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem); + OCC::SyncJournalFileRecord fileRecordBefore; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1") + DVSUFFIX, &fileRecordBefore)); + QVERIFY(fileRecordBefore.isValid()); + QVERIFY(!fileRecordBefore._lockstate._locked); + + const auto localFileNotLocked = QFileInfo{fakeFolder.localPath() + u"A/a1" + DVSUFFIX}; + QVERIFY(localFileNotLocked.isWritable()); + + fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226); + fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime()); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + + completeSpy.clear(); + QVERIFY(fakeFolder.syncOnce()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1") + DVSUFFIX)->_locked, OCC::SyncFileItem::LockStatus::LockedItem); + OCC::SyncJournalFileRecord fileRecordLocked; + QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1") + DVSUFFIX, &fileRecordLocked)); + QVERIFY(fileRecordLocked.isValid()); + QVERIFY(fileRecordLocked._lockstate._locked); + + const auto localFileLocked = QFileInfo{fakeFolder.localPath() + u"A/a1" + DVSUFFIX}; + QVERIFY(!localFileLocked.isWritable()); + } }; QTEST_GUILESS_MAIN(TestSyncVirtualFiles) From a7c163968aba615fb19d1109cfc1c73e10a5ebd0 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Fri, 18 Aug 2023 16:24:30 +0200 Subject: [PATCH 2/2] update read-only status of virtual files when needed for lock state Signed-off-by: Matthieu Gallien --- src/libsync/syncengine.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 656689a550e60..47d88fa689df3 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -389,6 +389,18 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) modificationHappened = true; } + if (item->_type == CSyncEnums::ItemTypeVirtualFile) { + if (item->_locked == SyncFileItem::LockStatus::LockedItem && (item->_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || item->_lockOwnerId != account()->davUser())) { + qCDebug(lcEngine()) << filePath << "file is locked: making it read only"; + FileSystem::setFileReadOnly(filePath, true); + } else { + qCDebug(lcEngine()) << filePath << "file is not locked: making it" + << ((!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) ? "read only" + : "read write"); + FileSystem::setFileReadOnlyWeak(filePath, (!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite))); + } + } + // Update on-disk virtual file metadata if (modificationHappened && item->_type == ItemTypeVirtualFile) { auto r = _syncOptions._vfs->updateMetadata(filePath, item->_modtime, item->_size, item->_fileId);