summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Meeks <michael.meeks@collabora.com>2019-11-19 22:51:45 +0000
committerMichael Meeks <michael.meeks@collabora.com>2019-11-20 15:42:05 +0000
commit0613e44c71c5ea7213ad6a0af332e26c4c7f1c90 (patch)
tree53f8b49eb4785138b6c903a7f6aa2e891c29e239
parentShare code for reuse_cookies handling. (diff)
downloadonline-feature/locking.tar.gz
online-feature/locking.zip
Initial wopi-like locking implementation. feature/locking
Change-Id: I3e16cfc40dbd29a24016b09e62afc9ec488300c7
-rw-r--r--wsd/DocumentBroker.cpp33
-rw-r--r--wsd/DocumentBroker.hpp2
-rw-r--r--wsd/Storage.cpp96
-rw-r--r--wsd/Storage.hpp48
4 files changed, 154 insertions, 25 deletions
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 6b393d277b..ef1ad7ed6e 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -189,6 +189,7 @@ DocumentBroker::DocumentBroker(const std::string& uri,
_poll(new DocumentBrokerPoll("docbroker_" + _docId, *this)),
_stop(false),
_closeReason("stopped"),
+ _lockCtx(new LockContext()),
_tileVersion(0),
_debugRenderedTileCount(0)
{
@@ -563,7 +564,8 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
WopiStorage* wopiStorage = dynamic_cast<WopiStorage*>(_storage.get());
if (wopiStorage != nullptr)
{
- std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo = wopiStorage->getWOPIFileInfo(session->getAuthorization());
+ std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo =
+ wopiStorage->getWOPIFileInfo(session->getAuthorization(), *_lockCtx);
userId = wopifileinfo->getUserId();
username = wopifileinfo->getUsername();
userExtraInfo = wopifileinfo->getUserExtraInfo();
@@ -722,7 +724,11 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
// Let's load the document now, if not loaded.
if (!_storage->isLoaded())
{
- std::string localPath = _storage->loadStorageFileToLocal(session->getAuthorization(), templateSource);
+ std::string localPath = _storage->loadStorageFileToLocal(
+ session->getAuthorization(), *_lockCtx, templateSource);
+
+ if (!_storage->updateLockState(session->getAuthorization(), *_lockCtx, true))
+ LOG_ERR("Failed to lock!");
#if !MOBILEAPP
// Check if we have a prefilter "plugin" for this document format
@@ -955,7 +961,8 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId, bool su
LOG_DBG("Persisting [" << _docKey << "] after saving to URI [" << uriAnonym << "].");
assert(_storage && _tileCache);
- StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(auth, saveAsPath, saveAsFilename, isRename);
+ StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(
+ auth, *_lockCtx, saveAsPath, saveAsFilename, isRename);
if (storageSaveResult.getResult() == StorageBase::SaveResult::OK)
{
if (!isSaveAs && !isRename)
@@ -1299,18 +1306,19 @@ size_t DocumentBroker::removeSession(const std::string& id)
LOG_ERR("Invalid or unknown session [" << id << "] to remove.");
return _sessions.size();
}
+ std::shared_ptr<ClientSession> session = it->second;
// Last view going away, can destroy.
_markToDestroy = (_sessions.size() <= 1);
- const bool lastEditableSession = !it->second->isReadOnly() && !haveAnotherEditableSession(id);
+ const bool lastEditableSession = !session->isReadOnly() && !haveAnotherEditableSession(id);
static const bool dontSaveIfUnmodified = !LOOLWSD::getConfigValue<bool>("per_document.always_save_on_exit", false);
LOG_INF("Removing session ["
<< id << "] on docKey [" << _docKey << "]. Have " << _sessions.size()
- << " sessions. IsReadOnly: " << it->second->isReadOnly()
- << ", IsViewLoaded: " << it->second->isViewLoaded() << ", IsWaitDisconnected: "
- << it->second->inWaitDisconnected() << ", MarkToDestroy: " << _markToDestroy
+ << " sessions. IsReadOnly: " << session->isReadOnly()
+ << ", IsViewLoaded: " << session->isViewLoaded() << ", IsWaitDisconnected: "
+ << session->inWaitDisconnected() << ", MarkToDestroy: " << _markToDestroy
<< ", LastEditableSession: " << lastEditableSession
<< ", dontSaveIfUnmodified: " << dontSaveIfUnmodified);
@@ -1341,7 +1349,16 @@ void DocumentBroker::disconnectSessionInternal(const std::string& id)
LOOLWSD::dumpEndSessionTrace(getJailId(), id, _uriOrig);
#endif
- LOG_TRC("Disconnect session internal " << id);
+ LOG_TRC("Disconnect session internal " << id <<
+ " destroy? " << _markToDestroy <<
+ " locked? " << _lockCtx->_isLocked);
+
+ if (_markToDestroy && // last session to remove; FIXME: Editable?
+ _lockCtx->_isLocked && _storage)
+ {
+ if (!_storage->updateLockState(it->second->getAuthorization(), *_lockCtx, false))
+ LOG_ERR("Failed to unlock!");
+ }
bool hardDisconnect;
if (it->second->inWaitDisconnected())
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 944120ca89..8641fe1233 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -36,6 +36,7 @@
// Forwards.
class PrisonerRequestDispatcher;
class DocumentBroker;
+class LockContext;
class StorageBase;
class TileCache;
class Message;
@@ -488,6 +489,7 @@ private:
std::unique_ptr<DocumentBrokerPoll> _poll;
std::atomic<bool> _stop;
std::string _closeReason;
+ std::unique_ptr<LockContext> _lockCtx;
/// Versioning is used to prevent races between
/// painting and invalidation.
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 5569763ee4..0825c4ef33 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -299,7 +299,7 @@ std::unique_ptr<LocalStorage::LocalFileInfo> LocalStorage::getLocalFileInfo()
return std::unique_ptr<LocalStorage::LocalFileInfo>(new LocalFileInfo({"localhost" + std::to_string(LastLocalStorageId), "LocalHost#" + std::to_string(LastLocalStorageId++)}));
}
-std::string LocalStorage::loadStorageFileToLocal(const Authorization& /*auth*/, const std::string& /*templateUri*/)
+std::string LocalStorage::loadStorageFileToLocal(const Authorization& /*auth*/, LockContext & /*lockCtx*/, const std::string& /*templateUri*/)
{
#if !MOBILEAPP
// /chroot/jailId/user/doc/childId/file.ext
@@ -363,7 +363,7 @@ std::string LocalStorage::loadStorageFileToLocal(const Authorization& /*auth*/,
}
-StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
+StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/, LockContext &/*lockCtx*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
{
try
{
@@ -461,7 +461,17 @@ std::string getReuseCookies(const Poco::URI &uriObject)
} // anonymous namespace
-std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Authorization& auth)
+void LockContext::initSupportsLocks()
+{
+ if (_supportsLocks)
+ return;
+
+ // first time token setup
+ _supportsLocks = true;
+ _lockToken = "lool-lock" + Util::rng::getHexString(8);
+}
+
+std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Authorization& auth, LockContext &lockCtx)
{
// update the access_token to the one matching to the session
Poco::URI uriObject(getUri());
@@ -657,6 +667,9 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
const std::chrono::system_clock::time_point modifiedTime = Util::iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime");
setFileInfo(FileInfo({filename, ownerId, modifiedTime, size}));
+ if (supportsLocks)
+ lockCtx.initSupportsLocks();
+
return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo(
{userId, obfuscatedUserId, userName, userExtraInfo, watermarkText, templateSaveAs, templateSource,
canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption,
@@ -667,8 +680,71 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
userCanRename, callDuration}));
}
+bool WopiStorage::updateLockState(const Authorization &auth, LockContext &lockCtx, bool lock)
+{
+ if (!lockCtx._supportsLocks)
+ return true;
+
+ Poco::URI uriObject(getUri());
+ auth.authorizeURI(uriObject);
+
+ std::string reuseStorageCookies = getReuseCookies(uriObject);
+
+ Poco::URI uriObjectAnonym(getUri());
+ uriObjectAnonym.setPath(LOOLWSD::anonymizeUrl(uriObjectAnonym.getPath()));
+ const std::string uriAnonym = uriObjectAnonym.toString();
+
+ const std::string wopiLog(lock ? "WOPI::Lock" : "WOPI::Unlock");
+ LOG_DBG(wopiLog << " requesting: " << uriAnonym);
+
+ try
+ {
+ std::unique_ptr<Poco::Net::HTTPClientSession> psession(getHTTPClientSession(uriObject));
+
+ Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1);
+ request.set("User-Agent", WOPI_AGENT_STRING);
+ auth.authorizeRequest(request);
+
+ request.set("X-WOPI-Override", lock ? "LOCK" : "UNLOCK");
+ request.set("X-WOPI-Lock", lockCtx._lockToken);
+ if (!getExtendedData().empty())
+ request.set("X-LOOL-WOPI-ExtendedData", getExtendedData());
+ addStorageDebugCookie(request);
+ if (_reuseCookies)
+ addStorageReuseCookie(request, reuseStorageCookies);
+
+ psession->sendRequest(request);
+ Poco::Net::HTTPResponse response;
+ std::istream& rs = psession->receiveResponse(response);
+
+ std::ostringstream oss;
+ Poco::StreamCopier::copyStream(rs, oss);
+ std::string responseString = oss.str();
+
+ LOG_INF(wopiLog << " response: " << responseString <<
+ " status " << response.getStatus());
+
+ if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK)
+ {
+ lockCtx._isLocked = lock;
+ return true;
+ }
+ else
+ {
+ LOG_WRN("Un-successfull " << wopiLog << " with status " << response.getStatus() <<
+ " and response: " << responseString);
+ }
+ }
+ catch (const Poco::Exception& pexc)
+ {
+ LOG_ERR("Cannot " << wopiLog << " uri [" << uriAnonym << "]. Error: " <<
+ pexc.displayText() << (pexc.nested() ? " (" + pexc.nested()->displayText() + ")" : ""));
+ }
+ return false;
+}
+
/// uri format: http://server/<...>/wopi*/files/<id>/content
-std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, const std::string& templateUri)
+std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, LockContext &/* lockCtx */, const std::string& templateUri)
{
// WOPI URI to download files ends in '/contents'.
// Add it here to get the payload instead of file info.
@@ -741,7 +817,6 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, const
ofs.close();
LOG_INF("WOPI::GetFile downloaded " << getFileSize(getRootFilePath()) << " bytes from [" <<
uriAnonym << "] -> " << getRootFilePathAnonym() << " in " << diff.count() << "s");
-
setLoaded(true);
// Now return the jailed path.
@@ -758,7 +833,7 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, const
return "";
}
-StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename)
+StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename)
{
// TODO: Check if this URI has write permission (canWrite = true)
@@ -791,6 +866,8 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
{
// normal save
request.set("X-WOPI-Override", "PUT");
+ if (lockCtx._supportsLocks)
+ request.set("X-WOPI-Lock", lockCtx._lockToken);
request.set("X-LOOL-WOPI-IsModifiedByUser", isUserModified()? "true": "false");
request.set("X-LOOL-WOPI-IsAutosave", getIsAutosave()? "true": "false");
request.set("X-LOOL-WOPI-IsExitSave", isExitSave()? "true": "false");
@@ -970,14 +1047,17 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
return saveResult;
}
-std::string WebDAVStorage::loadStorageFileToLocal(const Authorization& /*auth*/, const std::string& /*templateUri*/)
+std::string WebDAVStorage::loadStorageFileToLocal(
+ const Authorization& /*auth*/, LockContext &/*lockCtx*/, const std::string& /*templateUri*/)
{
// TODO: implement webdav GET.
setLoaded(true);
return getUri().toString();
}
-StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
+StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(
+ const Authorization& /*auth*/, LockContext &/*lockCtx*/, const std::string& /*saveAsPath*/,
+ const std::string& /*saveAsFilename*/, bool /*isRename*/)
{
// TODO: implement webdav PUT.
return StorageBase::SaveResult(StorageBase::SaveResult::OK);
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index 3632cd6f33..655e089b49 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -24,10 +24,28 @@
#include "Util.hpp"
#include <common/Authorization.hpp>
+/// Represents whether the underlying file is locked
+/// and with what token.
+struct LockContext
+{
+ /// Do we have support for locking for a storage.
+ bool _supportsLocks;
+ /// Do we own the (leased) lock currently
+ bool _isLocked;
+ /// Name if we need it to use consistently for locking
+ std::string _lockToken;
+
+ LockContext() : _supportsLocks(false), _isLocked(false) { }
+
+ /// one-time setup for supporting locks & create token
+ void initSupportsLocks();
+};
+
/// Base class of all Storage abstractions.
class StorageBase
{
public:
+
/// Represents basic file's attributes.
/// Used for local and network files.
class FileInfo
@@ -190,13 +208,16 @@ public:
std::string getFileExtension() const { return Poco::Path(_fileInfo.getFilename()).getExtension(); }
+ /// Update the locking state (check-in/out) of the associated file
+ virtual bool updateLockState(const Authorization &auth, LockContext &lockCtx, bool lock) = 0;
+
/// Returns a local file path for the given URI.
/// If necessary copies the file locally first.
- virtual std::string loadStorageFileToLocal(const Authorization& auth, const std::string& templateUri) = 0;
+ virtual std::string loadStorageFileToLocal(const Authorization& auth, LockContext &lockCtx, const std::string& templateUri) = 0;
/// Writes the contents of the file back to the source.
/// @param savedFile When the operation was saveAs, this is the path to the file that was saved.
- virtual SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) = 0;
+ virtual SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) = 0;
static size_t getFileSize(const std::string& filename);
@@ -283,9 +304,11 @@ public:
/// obtained using getFileInfo method
std::unique_ptr<LocalFileInfo> getLocalFileInfo();
- std::string loadStorageFileToLocal(const Authorization& auth, const std::string& templateUri) override;
+ bool updateLockState(const Authorization &, LockContext &, bool) override { return true; }
+
+ std::string loadStorageFileToLocal(const Authorization& auth, LockContext &lockCtx, const std::string& templateUri) override;
- SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
+ SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
private:
/// True if the jailed file is not linked but copied.
@@ -511,12 +534,17 @@ public:
/// provided during the initial creation of the WOPI storage.
/// Also extracts the basic file information from the response
/// which can then be obtained using getFileInfo()
- std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth);
+ /// Also sets up the locking context for future operations.
+ std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth,
+ LockContext &lockCtx);
+
+ /// Update the locking state (check-in/out) of the associated file
+ bool updateLockState(const Authorization &auth, LockContext &lockCtx, bool lock) override;
/// uri format: http://server/<...>/wopi*/files/<id>/content
- std::string loadStorageFileToLocal(const Authorization& auth, const std::string& templateUri) override;
+ std::string loadStorageFileToLocal(const Authorization& auth, LockContext &lockCtx, const std::string& templateUri) override;
- SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
+ SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
/// Total time taken for making WOPI calls during load
std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
@@ -546,9 +574,11 @@ public:
// Implement me
// WebDAVFileInfo getWebDAVFileInfo(const Poco::URI& uriPublic);
- std::string loadStorageFileToLocal(const Authorization& auth, const std::string& templateUri) override;
+ bool updateLockState(const Authorization &, LockContext &, bool) override { return true; }
+
+ std::string loadStorageFileToLocal(const Authorization& auth, LockContext &lockCtx, const std::string& templateUri) override;
- SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
+ SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
private:
std::unique_ptr<AuthBase> _authAgent;