wsd: use hostname, port and scheme in doc key

This allows us to use multiple hosts using same coolwsd instance.

added aliases configuration to coolwsd.xml to avoid
possibility of opening the same file as two if the
WOPI host is accessed using different aliases

Signed-off-by: Rash419 <rashesh.padia@collabora.com>
Change-Id: I32913015c15fd396cecc702b76e0dcaa8bcafad3
pull/4422/head cp-21.11.3-0
Rash419 2022-03-07 19:07:14 +05:30 committed by Gökay ŞATIR
parent 4bd6b39f71
commit d02dd19f33
6 changed files with 239 additions and 28 deletions

View File

@ -204,6 +204,13 @@
<locking desc="Locking settings">
<refresh desc="How frequently we should re-acquire a lock with the storage server, in seconds (default 15 mins) or 0 for no refresh" type="int" default="900">900</refresh>
</locking>
<!-- <group>
<host desc="hostname to allow or deny." allow="true">hostname</host>
<alias>aliasname1</alias>
<alias>aliasname2</alias>
</group> -->
</wopi>
<ssl desc="SSL settings">
<as_scheme type="bool" default="true" desc="When set we exclusively use the WOPI URI's scheme to enable SSL for storage">true</as_scheme>

View File

@ -102,6 +102,7 @@ wsd_sources = \
../common/Authorization.cpp \
../kit/Kit.cpp \
../kit/TestStubs.cpp \
../wsd/Storage.cpp \
../wsd/FileServerUtil.cpp \
../wsd/RequestDetails.cpp \
../wsd/TileCache.cpp \

View File

@ -1138,6 +1138,8 @@ public:
fetchWopiHostPatterns(newAppConfig, remoteJson);
fetchAliasGroups(newAppConfig, remoteJson);
#ifdef ENABLE_FEATURE_LOCK
fetchLockedHostPatterns(newAppConfig, remoteJson);
#endif
@ -1147,6 +1149,8 @@ public:
StorageBase::parseWopiHost(conf);
StorageBase::parseAliases(conf);
#ifdef ENABLE_FEATURE_LOCK
CommandControl::LockManager::parseLockedHost(conf);
#endif
@ -1158,7 +1162,7 @@ public:
//wopi host patterns
if (!conf.getBool("storage.wopi[@allow]", false))
{
LOG_INF("WOPI host feature is disabled in coolwsd.xml");
LOG_INF("WOPI host feature is disabled in configuration");
return;
}
try
@ -1211,7 +1215,7 @@ public:
{
if (!conf.getBool("feature_lock.locked_hosts[@allow]", false))
{
LOG_INF("locked_hosts feature is disabled from coolwsd.xml");
LOG_INF("locked_hosts feature is disabled from configuration");
return;
}
@ -1267,6 +1271,74 @@ public:
}
}
void fetchAliasGroups(std::map<std::string, std::string>& newAppConfig,
Poco::JSON::Object::Ptr remoteJson)
{
try
{
Poco::JSON::Array::Ptr aliasGroups =
remoteJson->getObject("storage")->getObject("wopi")->getArray("alias_groups");
if (aliasGroups->size() == 0)
{
LOG_WRN("Not overwriting any alias groups because alias_group array is empty");
return;
}
std::size_t i;
for (i = 0; i < aliasGroups->size(); i++)
{
Poco::JSON::Object::Ptr group = aliasGroups->getObject(i);
std::string host;
JsonUtil::findJSONValue(group, "host", host);
Poco::Dynamic::Var allow = group->get("allow");
const std::string path = "storage.wopi.group[" + std::to_string(i) + ']';
newAppConfig.insert(std::make_pair(path + ".host", host));
newAppConfig.insert(std::make_pair(path + ".host[@allow]", booleanToString(allow)));
Poco::JSON::Array::Ptr aliases = group->getArray("aliases");
auto it = aliases->begin();
size_t j;
for (j = 0; j < aliases->size(); j++)
{
const std::string aliasPath = path + ".alias[" + std::to_string(j) + ']';
newAppConfig.insert(std::make_pair(aliasPath, it->toString()));
it++;
}
for (;; j++)
{
const std::string aliasPath = path + ".alias[" + std::to_string(j) + ']';
if (!conf.has(aliasPath))
{
break;
}
newAppConfig.insert(std::make_pair(aliasPath, ""));
}
}
//if number of alias_groups defined in configuration are greater than number of alias_group
//fetched from json, overwrite the remaining alias_groups from config file to empty strings and
for (;; i++)
{
const std::string path = "storage.wopi.group[" + std::to_string(i) + "].host";
if (!conf.has(path))
{
break;
}
newAppConfig.insert(std::make_pair(path, ""));
newAppConfig.insert(std::make_pair(path + "[@allowed]", "false"));
}
}
catch (const std::exception& exc)
{
LOG_ERR("Fetching of alias groups failed with error: " << exc.what()
<< "please check JSON format");
}
}
//sets property to false if it is missing from JSON
//and returns std::string
std::string booleanToString(Poco::Dynamic::Var& booleanFlag)

View File

@ -10,6 +10,7 @@
#include "COOLWSD.hpp"
#include "RequestDetails.hpp"
#include "common/Log.hpp"
#include "Storage.hpp"
#include <Poco/URI.h>
#include "Exceptions.hpp"
@ -283,15 +284,15 @@ Poco::URI RequestDetails::sanitizeURI(const std::string& uri)
std::string RequestDetails::getDocKey(const Poco::URI& uri)
{
// If multiple host-names are used to access us, then
// they must be aliases. Permission to access aliased hosts
// is checked at the point of accepting incoming connections.
// At this point storing the hostname artificially discriminates
// between aliases and forces same document (when opened from
// alias hosts) to load as separate documents and sharing doesn't
// work. Worse, saving overwrites one another.
std::string docKey;
Poco::URI::encode(uri.getPath(), "", docKey);
std::string newUri = uri.getPath();
// resolve aliases
#if !MOBILEAPP
newUri = StorageBase::getNewUri(uri);
#endif
Poco::URI::encode(newUri, "", docKey);
LOG_INF("DocKey from URI [" << uri.toString() << "] => [" << docKey << ']');
return docKey;
}

View File

@ -65,6 +65,9 @@ bool StorageBase::WopiEnabled;
bool StorageBase::SSLAsScheme = true;
bool StorageBase::SSLEnabled = false;
Util::RegexListMatcher StorageBase::WopiHosts;
std::map<std::string, std::string> StorageBase::AliasHosts;
std::set<std::string> StorageBase::AllHosts;
std::string StorageBase::FirstHost;
#if !MOBILEAPP
@ -94,30 +97,115 @@ void StorageBase::parseWopiHost(Poco::Util::LayeredConfiguration& conf)
for (size_t i = 0;; ++i)
{
const std::string path = "storage.wopi.host[" + std::to_string(i) + ']';
const std::string host = conf.getString(path, "");
if (!host.empty())
{
if (conf.getBool(path + "[@allow]", false))
{
LOG_INF("Adding trusted WOPI host: [" << host << "].");
WopiHosts.allow(host);
}
else
{
LOG_INF("Adding blocked WOPI host: [" << host << "].");
WopiHosts.deny(host);
}
}
else if (!conf.has(path))
if (!conf.has(path))
{
break;
}
StorageBase::addWopiHost(conf.getString(path, ""), conf.getBool(path + "[@allow]", false));
}
}
}
void StorageBase::addWopiHost(std::string host, bool allow)
{
if (!host.empty())
{
if (allow)
{
LOG_INF("Adding trusted WOPI host: [" << host << "].");
WopiHosts.allow(host);
}
else
{
LOG_INF("Adding blocked WOPI host: [" << host << "].");
WopiHosts.deny(host);
}
}
}
void StorageBase::parseAliases(Poco::Util::LayeredConfiguration& conf)
{
AliasHosts.clear();
for (size_t i = 0;; i++)
{
const std::string path = "storage.wopi.group[" + std::to_string(i) + ']';
if (!conf.has(path + ".host"))
{
break;
}
const std::string hostAndPort = conf.getString(path + ".host", "");
if (hostAndPort.empty())
{
continue;
}
StringVector tokens = Util::tokenize(hostAndPort, ":");
bool allow = conf.getBool(path + ".host[@allow]", false);
StorageBase::addWopiHost(tokens[0], allow);
AllHosts.insert(tokens[0]);
for (size_t j = 0;; j++)
{
const std::string aliasPath = path + ".alias[" + std::to_string(j) + ']';
if (!conf.has(aliasPath))
{
break;
}
const std::string aliasHostAndPort = conf.getString(aliasPath, "");
if (!aliasHostAndPort.empty())
{
AliasHosts.insert({ aliasHostAndPort, hostAndPort });
}
tokens = Util::tokenize(aliasHostAndPort, ":");
AllHosts.insert(tokens[0]);
StorageBase::addWopiHost(tokens[0], allow);
}
}
}
std::string StorageBase::getNewUri(const Poco::URI& uri)
{
std::string uriHost = uri.getHost();
std::string uriPort = std::to_string(uri.getPort());
const std::string uriScheme = uri.getScheme();
const std::string uriPath = uri.getPath();
const std::string key = uriHost + ":" + uriPort;
if (AliasHosts.find(key) != AliasHosts.end())
{
const std::string aliasDetails = AliasHosts[key];
StringVector tokens = Util::tokenize(aliasDetails, ':');
if (!tokens[0].empty())
{
uriHost = tokens[0];
}
if (!tokens[1].empty())
{
uriPort = tokens[1];
}
}
std::string newUri;
if (!uriScheme.empty() && !uriHost.empty() && !Util::iequal(uriPort, "0"))
{
newUri = uriScheme + "://" + uriHost + ":" + uriPort + uriPath;
}
else
{
newUri = uri.getPath();
}
return newUri;
}
#endif
#if !defined(BUILDING_TESTS)
void StorageBase::initialize()
{
#if !MOBILEAPP
@ -126,6 +214,8 @@ void StorageBase::initialize()
parseWopiHost(app.config());
parseAliases(app.config());
#ifdef ENABLE_FEATURE_LOCK
CommandControl::LockManager::parseLockedHost(app.config());
#endif
@ -197,7 +287,30 @@ void StorageBase::initialize()
bool StorageBase::allowedWopiHost(const std::string& host)
{
return WopiEnabled && WopiHosts.match(host);
bool allow = WopiEnabled && WopiHosts.match(host);
if (!allow)
{
return false;
}
if (AliasHosts.empty())
{
if (FirstHost.empty())
{
FirstHost = host;
}
else if (FirstHost != host)
{
LOG_ERR("Only allowed host is: " << FirstHost
<< ", no aliases groups are defined in configuration");
return false;
}
}
else if (AllHosts.find(host) == AllHosts.end())
{
LOG_ERR("Host: " << host << " is not allowed, It is not part of aliases group");
return false;
}
return allow;
}
#if !MOBILEAPP
@ -272,7 +385,7 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
LOG_INF("Public URI [" << COOLWSD::anonymizeUrl(uri.toString()) << "] considered WOPI.");
const auto& targetHost = uri.getHost();
bool allowed(false);
if (WopiHosts.match(targetHost) || isLocalhost(targetHost))
if (StorageBase::allowedWopiHost(targetHost) || isLocalhost(targetHost))
{
allowed = true;
}
@ -282,7 +395,7 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
const auto hostAddresses(Poco::Net::DNS::resolve(targetHost));
for (auto &address : hostAddresses.addresses())
{
if (WopiHosts.match(address.toString()))
if (StorageBase::allowedWopiHost(address.toString()))
{
allowed = true;
break;
@ -1545,4 +1658,5 @@ WopiStorage::handleUploadToStorageResponse(const WopiUploadDetails& details,
#endif // !MOBILEAPP
#endif // !defined(BUILDING_TESTS)
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -330,6 +330,16 @@ public:
static void parseWopiHost(Poco::Util::LayeredConfiguration& conf);
static void parseAliases(Poco::Util::LayeredConfiguration& conf);
/// if request uri is an alias, replace request uri host and port with
/// original hostname and port defined by group tag from coolwsd.xml
/// to avoid possibility of opening the same file as two if the WOPI host
/// is accessed using different aliases
static std::string getNewUri(const Poco::URI& uri);
static void addWopiHost(std::string host, bool allow);
protected:
/// Sanitize a URI by removing authorization tokens.
@ -390,6 +400,12 @@ private:
static bool SSLEnabled;
/// Allowed/denied WOPI hosts, if any and if WOPI is enabled.
static Util::RegexListMatcher WopiHosts;
/// mapping of alias host and port to real host and port
static std::map<std::string, std::string> AliasHosts;
/// When group configuration is not defined only the firstHost gets access
static std::string FirstHost;
/// This contains all real and aliases host from group configuration
static std::set<std::string> AllHosts;
};
/// Trivial implementation of local storage that does not need do anything.