wsd: use WOPI host instance ID to support hostname aliases

The docKey creation moved to Storage where we first
invoke WOPI (if/when it's a WOPI-hosted doc and WOPI enabled)
and see if the user has access to the document at all.
If they do, we expect the server to give us a
unique ID to use for identifying the host regardless
of hostname aliases.

If a unique ID is not returned (i.e. empty or missing)
we use the hostname and port in its place as fallback.
This will break hostname aliases, but it will still work.

Change-Id: I407b0087395f9df6ad9cc6e037570487999be4a4
Reviewed-on: https://gerrit.libreoffice.org/37697
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
This commit is contained in:
Ashod Nakashian 2017-05-16 16:38:44 -04:00 committed by Ashod Nakashian
parent f94b42d9a8
commit ec2fd0844f
5 changed files with 98 additions and 21 deletions

View file

@ -736,7 +736,6 @@ bool ClientSession::forwardToClient(const std::shared_ptr<Message>& payload)
std::string ClientSession::getAccessToken() const
{
std::string accessToken;
Poco::URI::QueryParameters queryParams = _uriPublic.getQueryParameters();
for (auto& param: queryParams)
{

View file

@ -102,15 +102,7 @@ Poco::URI DocumentBroker::sanitizeURI(const std::string& uri)
std::string DocumentBroker::getDocKey(const Poco::URI& uri)
{
// If multiple host-names are used to access us, then
// we force same document (when opened from
// alias hosts) to load as separate documents and sharing doesn't
// work. Worse, saving overwrites one another.
// But we also do not want different WOPI hosts using the same path
// for some file getting shared across WOPI hosts
std::string docKey;
Poco::URI::encode(uri.getHost() + ":" + std::to_string(uri.getPort()) + uri.getPath(), "", docKey);
return docKey;
return StorageBase::getUniqueDocId(uri);
}
/// The Document Broker Poll - one of these in a thread per document
@ -409,6 +401,7 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
LOG_ERR("Failed to create Storage instance for [" << _docKey << "] in " << jailPath.toString());
return false;
}
firstInstance = true;
}
@ -423,6 +416,14 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo = wopiStorage->getWOPIFileInfo(session->getAccessToken());
userid = wopifileinfo->_userid;
username = wopifileinfo->_username;
if (firstInstance)
{
_hostInstanceId = wopifileinfo->_hostInstanceId;
}
else if (!_hostInstanceId.empty() && _hostInstanceId != wopifileinfo->_hostInstanceId)
{
throw UnauthorizedRequestException("Unauthorized WOPI host.");
}
if (!wopifileinfo->_userCanWrite)
{

View file

@ -369,6 +369,7 @@ private:
Poco::URI _uriJailed;
std::string _jailId;
std::string _filename;
std::string _hostInstanceId;
/// The last time we tried saving, regardless of whether the
/// document was modified and saved or not.

View file

@ -124,7 +124,7 @@ void StorageBase::initialize()
#endif
}
bool isLocalhost(const std::string& targetHost)
bool StorageBase::isLocalhost(const std::string& targetHost)
{
std::string targetAddress;
try
@ -201,7 +201,7 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
{
LOG_INF("Public URI [" << uri.toString() << "] considered WOPI.");
const auto& targetHost = uri.getHost();
if (WopiHosts.match(targetHost) || isLocalhost(targetHost))
if (isWopiHostAuthorized(targetHost))
{
return std::unique_ptr<StorageBase>(new WopiStorage(uri, jailRoot, jailPath));
}
@ -212,6 +212,39 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
throw BadRequestException("No Storage configured or invalid URI.");
}
std::string StorageBase::getUniqueDocId(const Poco::URI& uri)
{
std::string docId;
if (uri.isRelative() || uri.getScheme() == "file")
{
Poco::URI::encode(uri.getPath(), "", docId);
}
else if (WopiEnabled)
{
const auto& targetHost = uri.getHost();
if (isWopiHostAuthorized(targetHost))
{
std::string accessToken;
Poco::URI::QueryParameters queryParams = uri.getQueryParameters();
for (auto& param: queryParams)
{
if (param.first == "access_token")
accessToken = param.second;
}
const std::unique_ptr<WopiStorage::WOPIFileInfo> info = WopiStorage::getWOPIFileInfo(uri, accessToken);
const std::string prefix = !info->_hostInstanceId.empty()
? info->_hostInstanceId
: (uri.getHost() + ':' + std::to_string(uri.getPort()));
Poco::URI::encode(prefix + uri.getPath(), "", docId);
}
else
throw UnauthorizedRequestException("No acceptable WOPI hosts found matching the target host [" + targetHost + "] in config.");
}
return docId;
}
std::atomic<unsigned> LocalStorage::LastLocalStorageId;
std::unique_ptr<LocalStorage::LocalFileInfo> LocalStorage::getLocalFileInfo()
@ -406,10 +439,9 @@ void setQueryParameter(Poco::URI& uriObject, const std::string& key, const std::
} // anonymous namespace
std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const std::string& accessToken)
std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::URI uriObject, const std::string& accessToken)
{
// update the access_token to the one matching to the session
Poco::URI uriObject(_uri);
setQueryParameter(uriObject, "access_token", accessToken);
LOG_DBG("Getting info for wopi uri [" << uriObject.toString() << "].");
@ -456,6 +488,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st
std::string ownerId;
std::string userId;
std::string userName;
std::string hostInstanceId;
bool canWrite = false;
bool enableOwnerTermination = false;
std::string postMessageOrigin;
@ -490,6 +523,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st
getWOPIValue(object, "DisableExport", disableExport);
getWOPIValue(object, "DisableCopy", disableCopy);
getWOPIValue(object, "LastModifiedTime", lastModifiedTime);
getWOPIValue(object, "HostInstanceId", hostInstanceId);
}
else
{
@ -519,9 +553,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st
}
}
_fileInfo = FileInfo({filename, ownerId, modifiedTime, size});
return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration}));
return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo(filename, ownerId, modifiedTime, size, userId, userName, hostInstanceId, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration));
}
/// uri format: http://server/<...>/wopi*/files/<id>/content

View file

@ -100,11 +100,29 @@ public:
static std::unique_ptr<StorageBase> create(const Poco::URI& uri,
const std::string& jailRoot,
const std::string& jailPath);
/// Given the URI of a doc, return a unique doc ID.
/// Wopi host aliases are resolved to unique host ID.
static std::string getUniqueDocId(const Poco::URI& uri);
protected:
/// Returns the root path of the jail directory of docs.
std::string getLocalRootPath() const;
/// Returns true iff WOPI is enabled, and the host is whitelisted (or local).
static bool isWopiHostAuthorized(const std::string& host)
{
if (WopiEnabled)
{
return (WopiHosts.match(host) || isLocalhost(host));
}
return false;
}
static bool isLocalhost(const std::string& host);
protected:
const Poco::URI _uri;
std::string _localStorePath;
@ -176,11 +194,16 @@ public:
"], jailPath: [" << jailPath << "], uri: [" << uri.toString() << "].");
}
class WOPIFileInfo
class WOPIFileInfo : public FileInfo
{
public:
WOPIFileInfo(const std::string& userid,
WOPIFileInfo(const std::string& filename,
const std::string& ownerId,
const Poco::Timestamp& modifiedTime,
size_t size,
const std::string& userid,
const std::string& username,
const std::string& hostInstanceId,
const bool userCanWrite,
const std::string& postMessageOrigin,
const bool hidePrintOption,
@ -191,8 +214,10 @@ public:
const bool disableExport,
const bool disableCopy,
const std::chrono::duration<double> callDuration)
: _userid(userid),
: FileInfo(filename, ownerId, modifiedTime, size),
_userid(userid),
_username(username),
_hostInstanceId(hostInstanceId),
_userCanWrite(userCanWrite),
_postMessageOrigin(postMessageOrigin),
_hidePrintOption(hidePrintOption),
@ -210,6 +235,8 @@ public:
std::string _userid;
/// Display Name of user accessing the file
std::string _username;
/// Host instance ID (unique to the given host).
std::string _hostInstanceId;
/// If user accessing the file has write permission
bool _userCanWrite;
/// WOPI Post message property
@ -236,7 +263,12 @@ 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 std::string& accessToken);
std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const std::string& accessToken)
{
std::unique_ptr<WOPIFileInfo> info = getWOPIFileInfo(_uri, accessToken);
_fileInfo = FileInfo(info->_filename, info->_ownerId, info->_modifiedTime, info->_size);
return info;
}
/// uri format: http://server/<...>/wopi*/files/<id>/content
std::string loadStorageFileToLocal(const std::string& accessToken) override;
@ -246,6 +278,11 @@ public:
/// Total time taken for making WOPI calls during load
std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
/// Given the URI of a doc, return a unique doc ID.
static std::string getUniqueDocId(const Poco::URI& uri);
static std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(Poco::URI uriObject, const std::string& accessToken);
private:
// Time spend in loading the file from storage
std::chrono::duration<double> _wopiLoadDuration;
@ -273,6 +310,13 @@ public:
SaveResult saveLocalFileToStorage(const std::string& accessToken) override;
/// Given the URI of a doc, return a unique doc ID.
static std::string getUniqueDocId(const std::string& uri)
{
// TODO: Implement.
return uri;
}
private:
std::unique_ptr<AuthBase> _authAgent;
};