diff --git a/arangod/Cluster/ClusterInfo.cpp b/arangod/Cluster/ClusterInfo.cpp index 4290f77c56..9f157e2947 100644 --- a/arangod/Cluster/ClusterInfo.cpp +++ b/arangod/Cluster/ClusterInfo.cpp @@ -287,7 +287,7 @@ void ClusterInfo::cleanup() { theInstance->_plannedViews.clear(); theInstance->_plannedCollections.clear(); theInstance->_shards.clear(); - theInstance->_shardKeys.clear(); + theInstance->_shardToName.clear(); theInstance->_shardIds.clear(); theInstance->_currentCollections.clear(); } @@ -613,7 +613,7 @@ void ClusterInfo::loadPlan() { // > decltype(_shards) newShards; decltype(_shardServers) newShardServers; - decltype(_shardKeys) newShardKeys; + decltype(_shardToName) newShardToName; bool swapDatabases = false; bool swapCollections = false; @@ -926,15 +926,15 @@ void ClusterInfo::loadPlan() { databaseCollections.emplace(collectionId, newCollection); } - auto shardKeys = - std::make_shared>(newCollection->shardKeys()); - newShardKeys.emplace(collectionId, std::move(shardKeys)); - auto shardIDs = newCollection->shardIds(); auto shards = std::make_shared>(); + shards->reserve(shardIDs->size()); + newShardToName.reserve(shardIDs->size()); + for (auto const& p : *shardIDs) { shards->push_back(p.first); newShardServers.emplace(p.first, p.second); + newShardToName.emplace(p.first, newCollection->name()); } // Sort by the number in the shard ID ("s0000001" for example): std::sort(shards->begin(), shards->end(), @@ -990,7 +990,7 @@ void ClusterInfo::loadPlan() { if (swapCollections) { _plannedCollections.swap(newCollections); _shards.swap(newShards); - _shardKeys.swap(newShardKeys); + _shardToName.swap(newShardToName); _shardServers.swap(newShardServers); } if (swapViews) { @@ -3887,6 +3887,16 @@ arangodb::Result ClusterInfo::getShardServers(ShardID const& shardId, return arangodb::Result(TRI_ERROR_FAILED); } +CollectionID ClusterInfo::getCollectionNameForShard(ShardID const& shardId) { + READ_LOCKER(readLocker, _planProt.lock); + + auto it = _shardToName.find(shardId); + if (it != _shardToName.end()) { + return it->second; + } + return StaticStrings::Empty; +} + arangodb::Result ClusterInfo::agencyDump(std::shared_ptr body) { AgencyCommResult dump = _agency.dump(); body->add(dump.slice()); diff --git a/arangod/Cluster/ClusterInfo.h b/arangod/Cluster/ClusterInfo.h index 207f9a69ab..389fada6cc 100644 --- a/arangod/Cluster/ClusterInfo.h +++ b/arangod/Cluster/ClusterInfo.h @@ -648,6 +648,9 @@ class ClusterInfo { */ arangodb::Result getShardServers(ShardID const& shardId, std::vector&); + /// @brief map shardId to collection name (not ID) + CollectionID getCollectionNameForShard(ShardID const& shardId); + ////////////////////////////////////////////////////////////////////////////// /// @brief get an operation timeout ////////////////////////////////////////////////////////////////////////////// @@ -751,8 +754,7 @@ class ClusterInfo { ProtectionData _planProt; uint64_t _planVersion; // This is the version in the Plan which underlies - // the data in _plannedCollections, _shards and - // _shardKeys + // the data in _plannedCollections and _shards. uint64_t _currentVersion; // This is the version in Current which underlies // the data in _currentDatabases, // _currentCollections and _shardsIds @@ -761,7 +763,7 @@ class ClusterInfo { // We need information about collections, again we have // data from Plan and from Current. - // The information for _shards and _shardKeys are filled from the + // The information for _shards and _shardToName are filled from the // Plan (since they are fixed for the lifetime of the collection). // _shardIds is filled from Current, since we have to be able to // move shards between servers, and Plan contains who ought to be @@ -773,11 +775,10 @@ class ClusterInfo { std::shared_ptr>> _shards; // from Plan/Collections/ // (may later come from Current/Collections/ ) - std::unordered_map>> - _shardKeys; // from Plan/Collections/ // planned shard => servers map std::unordered_map> _shardServers; + // planned shard ID => collection name + std::unordered_map _shardToName; AllViews _plannedViews; // from Plan/Views/ AllViews _newPlannedViews; // views that have been created during `loadPlan` execution diff --git a/arangod/RestHandler/RestReplicationHandler.cpp b/arangod/RestHandler/RestReplicationHandler.cpp index 4d6aaea9b8..79e06b8d30 100644 --- a/arangod/RestHandler/RestReplicationHandler.cpp +++ b/arangod/RestHandler/RestReplicationHandler.cpp @@ -112,15 +112,12 @@ static bool ignoreHiddenEnterpriseCollection(std::string const& name, bool force static Result checkPlanLeaderDirect(std::shared_ptr const& col, std::string const& claimLeaderId) { - - std::vector agencyPath = { - "Plan", - "Collections", - col->vocbase().name(), - std::to_string(col->planId()), - "shards", - col->name() - }; + std::vector agencyPath = {"Plan", + "Collections", + col->vocbase().name(), + std::to_string(col->planId()), + "shards", + col->name()}; std::string shardAgencyPathString = StringUtils::join(agencyPath, '/'); @@ -245,6 +242,12 @@ bool RestReplicationHandler::isCoordinatorError() { // main function that dispatches the different routes and commands RestStatus RestReplicationHandler::execute() { + auto res = testPermissions(); + if (!res.ok()) { + generateError(res); + return RestStatus::DONE; + } + // extract the request type auto const type = _request->requestType(); auto const& suffixes = _request->suffixes(); @@ -547,9 +550,97 @@ BAD_CALL: return RestStatus::DONE; } - ////////////////////////////////////////////////////////////////////////////// - /// @brief update the leader of a shard - ////////////////////////////////////////////////////////////////////////////// +Result RestReplicationHandler::testPermissions() { + if (!_request->authenticated()) { + return TRI_ERROR_NO_ERROR; + } + + std::string user = _request->user(); + auto const& suffixes = _request->suffixes(); + size_t const len = suffixes.size(); + if (len >= 1) { + auto const type = _request->requestType(); + std::string const& command = suffixes[0]; + if ((command == "batch") || (command == "inventory" && type == rest::RequestType::GET) || + (command == "dump" && type == rest::RequestType::GET) || + (command == "restore-collection" && type == rest::RequestType::PUT)) { + if (command == "dump") { + // check dump collection permissions (at least ro needed) + std::string collectionName = _request->value("collection"); + + if (ServerState::instance()->isCoordinator()) { + // We have a shard id, need to translate + auto ci = ClusterInfo::instance(); + collectionName = ci->getCollectionNameForShard(collectionName); + } + + if (!collectionName.empty()) { + auto& exec = ExecContext::CURRENT; + ExecContextSuperuserScope escope(exec->isAdminUser()); + if (!exec->isAdminUser() && + !exec->canUseCollection(collectionName, auth::Level::RO)) { + // not enough rights + return Result(TRI_ERROR_FORBIDDEN); + } + } else { + // not found, return 404 + return Result(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND); + } + } else if (command == "restore-collection") { + VPackSlice const slice = _request->payload(); + VPackSlice const parameters = slice.get("parameters"); + if (parameters.isObject()) { + if (parameters.get("name").isString()) { + std::string collectionName = parameters.get("name").copyString(); + if (!collectionName.empty()) { + std::string dbName = _request->databaseName(); + TRI_vocbase_t* vocbase = DatabaseFeature::DATABASE->lookupDatabase(dbName); + if (vocbase == nullptr) { + return Result(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); + } + + std::string const& overwriteCollection = + _request->value("overwrite"); + + auto& exec = ExecContext::CURRENT; + ExecContextSuperuserScope escope(exec->isSuperuser()); + + if (overwriteCollection == "true" || + vocbase->lookupCollection(collectionName) == nullptr) { + // 1.) re-create collection, means: overwrite=true (rw database) + // OR 2.) not existing, new collection (rw database) + if (!exec->isAdminUser() && !exec->canUseDatabase(dbName, auth::Level::RW)) { + return Result(TRI_ERROR_FORBIDDEN); + } + } else { + // 3u) Existing collection (ro database, rw collection) + // no overwrite. restoring into an existing collection + if (!exec->isAdminUser() && + !exec->canUseCollection(collectionName, auth::Level::RW)) { + return Result(TRI_ERROR_FORBIDDEN); + } + } + } else { + return Result(TRI_ERROR_HTTP_BAD_PARAMETER, + "empty collection name"); + } + } + } else { + return Result(TRI_ERROR_HTTP_BAD_PARAMETER, + "invalid collection name type"); + } + } else { + return Result(TRI_ERROR_HTTP_BAD_PARAMETER, + "invalid collection parameter type"); + } + } + } + return Result(TRI_ERROR_NO_ERROR); +} + +////////////////////////////////////////////////////////////////////////////// +/// @brief update the leader of a shard +////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandSetTheLeader() { TRI_ASSERT(ServerState::instance()->isDBServer()); @@ -596,7 +687,8 @@ void RestReplicationHandler::handleCommandSetTheLeader() { } if (!oldLeaderIdSlice.isEqualString(currentLeader)) { - generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN, "old leader not as expected"); + generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN, + "old leader not as expected"); return; } @@ -887,9 +979,9 @@ void RestReplicationHandler::handleCommandRestoreCollection() { if (name.isObject()) { name = name.get("name"); if (name.isString() && !name.copyString().empty() && name.copyString()[0] == '_') { - // system collection... it may have been created in the meantime by a background action - // collection should be there now - as long as the collection is there in the end - // we are satisfied + // system collection... it may have been created in the meantime by a + // background action collection should be there now - as long as the + // collection is there in the end we are satisfied res.reset(); } } @@ -1250,15 +1342,22 @@ Result RestReplicationHandler::processRestoreCollectionCoordinator( s = parameters.get(StaticStrings::ReplicationFactor); if (s.isString() && s.copyString() == "satellite") { // set "satellite" replicationFactor to the default replication factor - ClusterFeature* cl = application_features::ApplicationServer::getFeature("Cluster"); + ClusterFeature* cl = application_features::ApplicationServer::getFeature( + "Cluster"); uint32_t replicationFactor = cl->systemReplicationFactor(); toMerge.add(StaticStrings::ReplicationFactor, VPackValue(replicationFactor)); - changes.push_back(std::string("changed 'replicationFactor' attribute value to ") + std::to_string(replicationFactor));; + changes.push_back( + std::string("changed 'replicationFactor' attribute value to ") + + std::to_string(replicationFactor)); + ; } if (!changes.empty()) { - LOG_TOPIC(INFO, Logger::CLUSTER) << "rewrote info for collection '" << name << "' on restore for usage with the community version. the following changes were applied: " << basics::StringUtils::join(changes, ". "); + LOG_TOPIC(INFO, Logger::CLUSTER) + << "rewrote info for collection '" + << name << "' on restore for usage with the community version. the following changes were applied: " + << basics::StringUtils::join(changes, ". "); } #endif @@ -2384,7 +2483,7 @@ void RestReplicationHandler::handleCommandAddFollower() { col->followers()->add(followerId); - { // untrack the (async) replication client, so the WAL may be cleaned + { // untrack the (async) replication client, so the WAL may be cleaned TRI_server_id_t const serverId = StringUtils::uint64( basics::VelocyPackHelper::getStringValue(body, "serverId", "")); SyncerId const syncerId = SyncerId{StringUtils::uint64( diff --git a/arangod/RestHandler/RestReplicationHandler.h b/arangod/RestHandler/RestReplicationHandler.h index b454c5b091..27c11a63d9 100644 --- a/arangod/RestHandler/RestReplicationHandler.h +++ b/arangod/RestHandler/RestReplicationHandler.h @@ -458,6 +458,13 @@ class RestReplicationHandler : public RestVocbaseBaseHandler { ////////////////////////////////////////////////////////////////////////////// ResultT cancelBlockingTransaction(aql::QueryId id) const; + + ////////////////////////////////////////////////////////////////////////////// + /// @brief Validate that the requesting user has access rights to this route + /// Will return TRI_ERROR_NO_ERROR if user has access + /// Will return error code otherwise. + ////////////////////////////////////////////////////////////////////////////// + Result testPermissions(); }; } // namespace arangodb #endif diff --git a/arangod/Utils/ExecContext.h b/arangod/Utils/ExecContext.h index f00bbe5b97..a0ce201958 100644 --- a/arangod/Utils/ExecContext.h +++ b/arangod/Utils/ExecContext.h @@ -39,6 +39,7 @@ class Methods; /// context for convencience class ExecContext : public RequestContext { protected: + friend struct ExecContextSuperuserScope; enum class Type { Default, Internal }; ExecContext(ExecContext::Type type, std::string const& user, @@ -143,6 +144,7 @@ class ExecContext : public RequestContext { /// level of current database auth::Level _databaseAuthLevel; + private: static ExecContext SUPERUSER; }; @@ -158,6 +160,25 @@ struct ExecContextScope { private: ExecContext const* _old; }; + +struct ExecContextSuperuserScope { + explicit ExecContextSuperuserScope() + : _old(ExecContext::CURRENT) { + ExecContext::CURRENT = &ExecContext::SUPERUSER; + } + + explicit ExecContextSuperuserScope(bool cond) : _old(ExecContext::CURRENT) { + if (cond) { + ExecContext::CURRENT = &ExecContext::SUPERUSER; + } + } + + ~ExecContextSuperuserScope() { ExecContext::CURRENT = _old; } + + private: + ExecContext const* _old; +}; + } // namespace arangodb #endif