diff --git a/arangod/Actions/RestActionHandler.cpp b/arangod/Actions/RestActionHandler.cpp index b7dcfc0b64e8..142b7583c42b 100644 --- a/arangod/Actions/RestActionHandler.cpp +++ b/arangod/Actions/RestActionHandler.cpp @@ -24,6 +24,7 @@ #include "RestActionHandler.h" #include "Actions/actions.h" +#include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Statistics/RequestStatistics.h" #include "VocBase/vocbase.h" @@ -40,7 +41,7 @@ RestActionHandler::RestActionHandler(application_features::ApplicationServer& se RestStatus RestActionHandler::execute() { // check the request path - if (_request->databaseName() == TRI_VOC_SYSTEM_DATABASE) { + if (_request->databaseName() == StaticStrings::SystemDatabase) { if (StringUtils::isPrefix(_request->requestPath(), "/_admin/aardvark")) { RequestStatistics::SET_IGNORE(_statistics); } diff --git a/arangod/Auth/User.cpp b/arangod/Auth/User.cpp index 41c47b57227f..a89a592b6e85 100644 --- a/arangod/Auth/User.cpp +++ b/arangod/Auth/User.cpp @@ -559,7 +559,7 @@ auth::Level auth::User::collectionAuthLevel(std::string const& dbname, bool isSystem = cname[0] == '_'; if (isSystem) { // disallow access to _system/_users for everyone - if (dbname == TRI_VOC_SYSTEM_DATABASE && cname == TRI_COL_NAME_USERS) { + if (dbname == StaticStrings::SystemDatabase && cname == TRI_COL_NAME_USERS) { return auth::Level::NONE; } else if (cname == "_queues") { return auth::Level::RO; diff --git a/arangod/Cluster/ClusterInfo.cpp b/arangod/Cluster/ClusterInfo.cpp index 4c008ff31860..60bcf37de4f6 100644 --- a/arangod/Cluster/ClusterInfo.cpp +++ b/arangod/Cluster/ClusterInfo.cpp @@ -32,6 +32,7 @@ #include "Basics/MutexLocker.h" #include "Basics/NumberUtils.h" #include "Basics/RecursiveLocker.h" +#include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Basics/WriteLocker.h" @@ -1759,7 +1760,7 @@ Result ClusterInfo::dropDatabaseCoordinator( // drop database double timeout // request timeout ) { TRI_ASSERT(ServerState::instance()->isCoordinator()); - if (name == TRI_VOC_SYSTEM_DATABASE) { + if (name == StaticStrings::SystemDatabase) { return Result(TRI_ERROR_FORBIDDEN); } diff --git a/arangod/MMFiles/MMFilesEngine.cpp b/arangod/MMFiles/MMFilesEngine.cpp index 3f77402ba154..778e4ccde1b3 100644 --- a/arangod/MMFiles/MMFilesEngine.cpp +++ b/arangod/MMFiles/MMFilesEngine.cpp @@ -29,6 +29,7 @@ #include "Basics/MutexLocker.h" #include "Basics/MutexUnlocker.h" #include "Basics/ReadLocker.h" +#include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Basics/WriteLocker.h" @@ -264,7 +265,7 @@ void MMFilesEngine::start() { if (names.empty()) { // no databases found, i.e. there is no system database! // create a database for the system database - int res = createDatabaseDirectory(TRI_NewTickServer(), TRI_VOC_SYSTEM_DATABASE); + int res = createDatabaseDirectory(TRI_NewTickServer(), StaticStrings::SystemDatabase); if (res != TRI_ERROR_NO_ERROR) { LOG_TOPIC("982c7", ERR, arangodb::Logger::ENGINES) diff --git a/arangod/Replication/GlobalInitialSyncer.cpp b/arangod/Replication/GlobalInitialSyncer.cpp index 8ab791e287c6..227a0583c4a5 100644 --- a/arangod/Replication/GlobalInitialSyncer.cpp +++ b/arangod/Replication/GlobalInitialSyncer.cpp @@ -25,6 +25,7 @@ #include "ApplicationFeatures/ApplicationServer.h" #include "Basics/Result.h" +#include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Replication/DatabaseInitialSyncer.h" @@ -50,7 +51,7 @@ using namespace arangodb::rest; GlobalInitialSyncer::GlobalInitialSyncer(ReplicationApplierConfiguration const& configuration) : InitialSyncer(configuration) { // has to be set here, otherwise broken - _state.databaseName = TRI_VOC_SYSTEM_DATABASE; + _state.databaseName = StaticStrings::SystemDatabase; } GlobalInitialSyncer::~GlobalInitialSyncer() { diff --git a/arangod/Replication/GlobalTailingSyncer.cpp b/arangod/Replication/GlobalTailingSyncer.cpp index 6d5f79ecf425..979972366059 100644 --- a/arangod/Replication/GlobalTailingSyncer.cpp +++ b/arangod/Replication/GlobalTailingSyncer.cpp @@ -22,6 +22,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "GlobalTailingSyncer.h" +#include "Basics/StaticStrings.h" #include "Basics/Thread.h" #include "Logger/LogMacros.h" #include "Logger/Logger.h" @@ -43,7 +44,7 @@ GlobalTailingSyncer::GlobalTailingSyncer(ReplicationApplierConfiguration const& configuration, initialTick, useTick, barrierId), _queriedTranslations(false) { _ignoreDatabaseMarkers = false; - _state.databaseName = TRI_VOC_SYSTEM_DATABASE; + _state.databaseName = StaticStrings::SystemDatabase; } std::string GlobalTailingSyncer::tailingBaseUrl(std::string const& command) { diff --git a/arangod/Replication/TailingSyncer.cpp b/arangod/Replication/TailingSyncer.cpp index 702c45e5012b..0f0c872888d1 100644 --- a/arangod/Replication/TailingSyncer.cpp +++ b/arangod/Replication/TailingSyncer.cpp @@ -334,7 +334,7 @@ Result TailingSyncer::processDBMarker(TRI_replication_operation_e type, TRI_vocbase_t* vocbase = DatabaseFeature::DATABASE->lookupDatabase(name); - if (vocbase != nullptr && name != TRI_VOC_SYSTEM_DATABASE) { + if (vocbase != nullptr && name != StaticStrings::SystemDatabase) { LOG_TOPIC("0a3a4", WARN, Logger::REPLICATION) << "seeing database creation marker " << "for an already existing db. Dropping db..."; @@ -357,7 +357,7 @@ Result TailingSyncer::processDBMarker(TRI_replication_operation_e type, } else if (type == REPLICATION_DATABASE_DROP) { TRI_vocbase_t* vocbase = DatabaseFeature::DATABASE->lookupDatabase(name); - if (vocbase != nullptr && name != TRI_VOC_SYSTEM_DATABASE) { + if (vocbase != nullptr && name != StaticStrings::SystemDatabase) { // abort all ongoing transactions for the database to be dropped abortOngoingTransactions(name); diff --git a/arangod/RestHandler/RestAdminClusterHandler.cpp b/arangod/RestHandler/RestAdminClusterHandler.cpp index ac7454906fb7..fe15ac3e2d0a 100644 --- a/arangod/RestHandler/RestAdminClusterHandler.cpp +++ b/arangod/RestHandler/RestAdminClusterHandler.cpp @@ -272,10 +272,10 @@ std::string const RestAdminClusterHandler::RemoveServer = "removeServer"; std::string const RestAdminClusterHandler::RebalanceShards = "rebalanceShards"; RestStatus RestAdminClusterHandler::execute() { - if (!ExecContext::current().isAdminUser()) { - generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; - } + // No more check for admin rights here, since we handle this in every individual + // method below. Some of them do no longer require admin access + // (e.g. /_admin/cluster/health). If you add a new API below here, please + // make sure to check for permissions! auto const& suffixes = request()->suffixes(); size_t const len = suffixes.size(); @@ -965,6 +965,11 @@ RestStatus RestAdminClusterHandler::handleShardDistribution() { return RestStatus::DONE; } + if (!ExecContext::current().isAdminUser()) { + generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); + return RestStatus::DONE; + } + if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); return RestStatus::DONE; @@ -1014,6 +1019,11 @@ RestStatus RestAdminClusterHandler::handleCollectionShardDistribution() { return RestStatus::DONE; } + if (!ExecContext::current().isAdminUser()) { + generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); + return RestStatus::DONE; + } + switch (request()->requestType()) { case rest::RequestType::GET: return handleGetCollectionShardDistribution( @@ -1392,7 +1402,8 @@ RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { } RestStatus RestAdminClusterHandler::handleNumberOfServers() { - if (!ServerState::instance()->isCoordinator()) { + if (!ServerState::instance()->isCoordinator() || + !ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); return RestStatus::DONE; @@ -1410,11 +1421,9 @@ RestStatus RestAdminClusterHandler::handleNumberOfServers() { } RestStatus RestAdminClusterHandler::handleHealth() { - if (!ExecContext::current().isAdminUser()) { - generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; - } - + // We allow this API whenever one is authenticated in some way. There used + // to be a check for isAdminUser here. However, we want that the UI with + // the cluster health dashboard works for every authenticated user. if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); return RestStatus::DONE; diff --git a/arangod/RestHandler/RestAdminServerHandler.cpp b/arangod/RestHandler/RestAdminServerHandler.cpp index f9a165156fc0..a1484afe92ce 100644 --- a/arangod/RestHandler/RestAdminServerHandler.cpp +++ b/arangod/RestHandler/RestAdminServerHandler.cpp @@ -25,6 +25,7 @@ #include "Actions/RestActionHandler.h" #include "ApplicationFeatures/ApplicationServer.h" +#include "Basics/StaticStrings.h" #include "GeneralServer/AuthenticationFeature.h" #include "GeneralServer/GeneralServerFeature.h" #include "GeneralServer/SslServerFeature.h" @@ -159,7 +160,7 @@ void RestAdminServerHandler::handleMode() { if (af->isActive() && !_request->user().empty()) { auth::Level lvl; if (af->userManager() != nullptr) { - lvl = af->userManager()->databaseAuthLevel(_request->user(), TRI_VOC_SYSTEM_DATABASE, + lvl = af->userManager()->databaseAuthLevel(_request->user(), StaticStrings::SystemDatabase, /*configured*/ true); } else { lvl = auth::Level::RW; diff --git a/arangod/RestHandler/RestReplicationHandler.cpp b/arangod/RestHandler/RestReplicationHandler.cpp index 31bc98c7c825..40c76615ab97 100644 --- a/arangod/RestHandler/RestReplicationHandler.cpp +++ b/arangod/RestHandler/RestReplicationHandler.cpp @@ -1049,7 +1049,9 @@ Result RestReplicationHandler::processRestoreCollection(VPackSlice const& collec return Result(); } - ExecContextSuperuserScope escope(ExecContext::current().isAdminUser()); + ExecContextSuperuserScope escope( + ExecContext::current().isSuperuser() || + (ExecContext::current().isAdminUser() && !ServerState::readOnly())); auto* col = _vocbase.lookupCollection(name).get(); @@ -1427,7 +1429,9 @@ Result RestReplicationHandler::processRestoreData(std::string const& colName) { bool generateNewRevisionIds = !_request->parsedValue(StaticStrings::PreserveRevisionIds, false); - ExecContextSuperuserScope escope(ExecContext::current().isAdminUser()); + ExecContextSuperuserScope escope( + ExecContext::current().isSuperuser() || + (ExecContext::current().isAdminUser() && !ServerState::readOnly())); if (colName == TRI_COL_NAME_USERS) { // We need to handle the _users in a special way @@ -1898,7 +1902,10 @@ Result RestReplicationHandler::processRestoreIndexes(VPackSlice const& collectio Result fres; - ExecContextSuperuserScope escope(ExecContext::current().isAdminUser()); + ExecContextSuperuserScope escope( + ExecContext::current().isSuperuser() || + (ExecContext::current().isAdminUser() && !ServerState::readOnly())); + READ_LOCKER(readLocker, _vocbase._inventoryLock); // look up the collection diff --git a/arangod/RestServer/DatabaseFeature.cpp b/arangod/RestServer/DatabaseFeature.cpp index 26d5d89e5d25..3b25fa812dbe 100644 --- a/arangod/RestServer/DatabaseFeature.cpp +++ b/arangod/RestServer/DatabaseFeature.cpp @@ -33,6 +33,7 @@ #include "Basics/FileUtils.h" #include "Basics/MutexLocker.h" #include "Basics/NumberUtils.h" +#include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Basics/WriteLocker.h" @@ -377,7 +378,7 @@ void DatabaseFeature::start() { FATAL_ERROR_EXIT(); } - if (!lookupDatabase(TRI_VOC_SYSTEM_DATABASE)) { + if (!lookupDatabase(StaticStrings::SystemDatabase)) { LOG_TOPIC("97e7c", FATAL, arangodb::Logger::FIXME) << "No _system database found in database directory. Cannot start!"; FATAL_ERROR_EXIT(); @@ -740,7 +741,7 @@ Result DatabaseFeature::createDatabase(CreateDatabaseInfo&& info, TRI_vocbase_t* /// @brief drop database int DatabaseFeature::dropDatabase(std::string const& name, bool waitForDeletion, bool removeAppsDirectory) { - if (name == TRI_VOC_SYSTEM_DATABASE) { + if (name == StaticStrings::SystemDatabase) { // prevent deletion of system database events::DropDatabase(name, TRI_ERROR_FORBIDDEN); return TRI_ERROR_FORBIDDEN; @@ -892,7 +893,7 @@ std::vector DatabaseFeature::getDatabaseIds(bool includeSystem) if (vocbase->isDropped()) { continue; } - if (includeSystem || vocbase->name() != TRI_VOC_SYSTEM_DATABASE) { + if (includeSystem || vocbase->name() != StaticStrings::SystemDatabase) { ids.emplace_back(vocbase->id()); } } @@ -1095,7 +1096,7 @@ void DatabaseFeature::updateContexts() { return; } - auto* vocbase = useDatabase(TRI_VOC_SYSTEM_DATABASE); + auto* vocbase = useDatabase(StaticStrings::SystemDatabase); TRI_ASSERT(vocbase); auto queryRegistry = QueryRegistryFeature::registry(); diff --git a/arangod/RestServer/SystemDatabaseFeature.cpp b/arangod/RestServer/SystemDatabaseFeature.cpp index 9190b1fed3f5..394910be474b 100644 --- a/arangod/RestServer/SystemDatabaseFeature.cpp +++ b/arangod/RestServer/SystemDatabaseFeature.cpp @@ -24,6 +24,7 @@ #include "SystemDatabaseFeature.h" #include "ApplicationFeatures/ApplicationServer.h" +#include "Basics/StaticStrings.h" #include "Basics/application-exit.h" #include "Logger/LogMacros.h" #include "RestServer/DatabaseFeature.h" @@ -55,7 +56,7 @@ SystemDatabaseFeature::SystemDatabaseFeature(application_features::ApplicationSe void SystemDatabaseFeature::start() { if (server().hasFeature()) { auto& feature = server().getFeature(); - _vocbase.store(feature.lookupDatabase(TRI_VOC_SYSTEM_DATABASE)); + _vocbase.store(feature.lookupDatabase(StaticStrings::SystemDatabase)); return; } diff --git a/arangod/RestServer/VocbaseContext.cpp b/arangod/RestServer/VocbaseContext.cpp index 2de84d4a671b..a1a4f774a028 100644 --- a/arangod/RestServer/VocbaseContext.cpp +++ b/arangod/RestServer/VocbaseContext.cpp @@ -22,6 +22,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "VocbaseContext.h" +#include "Basics/StaticStrings.h" #include "Cluster/ServerState.h" #include "GeneralServer/AuthenticationFeature.h" #include "Logger/LogMacros.h" @@ -35,8 +36,8 @@ namespace arangodb { VocbaseContext::VocbaseContext(GeneralRequest& req, TRI_vocbase_t& vocbase, ExecContext::Type type, auth::Level systemLevel, - auth::Level dbLevel) - : ExecContext(type, req.user(), req.databaseName(), systemLevel, dbLevel), + auth::Level dbLevel, bool isAdminUser) + : ExecContext(type, req.user(), req.databaseName(), systemLevel, dbLevel, isAdminUser), _vocbase(vocbase) { // _vocbase has already been refcounted for us TRI_ASSERT(!_vocbase.isDangling()); @@ -58,7 +59,7 @@ VocbaseContext* VocbaseContext::create(GeneralRequest& req, TRI_vocbase_t& vocba if (isSuperUser) { return new VocbaseContext(req, vocbase, ExecContext::Type::Internal, /*sysLevel*/ auth::Level::RW, - /*dbLevel*/ auth::Level::RW); + /*dbLevel*/ auth::Level::RW, true); } AuthenticationFeature* auth = AuthenticationFeature::instance(); @@ -68,19 +69,21 @@ VocbaseContext* VocbaseContext::create(GeneralRequest& req, TRI_vocbase_t& vocba // special read-only case return new VocbaseContext(req, vocbase, ExecContext::Type::Internal, /*sysLevel*/ auth::Level::RO, - /*dbLevel*/ auth::Level::RO); + /*dbLevel*/ auth::Level::RO, true); } return new VocbaseContext(req, vocbase, req.user().empty() ? ExecContext::Type::Internal : ExecContext::Type::Default, /*sysLevel*/ auth::Level::RW, - /*dbLevel*/ auth::Level::RW); + /*dbLevel*/ auth::Level::RW, true); } if (!req.authenticated()) { return new VocbaseContext(req, vocbase, ExecContext::Type::Default, /*sysLevel*/ auth::Level::NONE, - /*dbLevel*/ auth::Level::NONE); - } else if (req.user().empty()) { + /*dbLevel*/ auth::Level::NONE, false); + } + + if (req.user().empty()) { std::string msg = "only jwt can be used to authenticate as superuser"; LOG_TOPIC("2d0f6", WARN, Logger::AUTHENTICATION) << msg; THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, msg); @@ -93,26 +96,32 @@ VocbaseContext* VocbaseContext::create(GeneralRequest& req, TRI_vocbase_t& vocba return nullptr; } - auth::Level dbLvl = um->databaseAuthLevel(req.user(), req.databaseName()); + auth::Level dbLvl = um->databaseAuthLevel(req.user(), req.databaseName(), false); auth::Level sysLvl = dbLvl; - if (req.databaseName() != TRI_VOC_SYSTEM_DATABASE) { - sysLvl = um->databaseAuthLevel(req.user(), TRI_VOC_SYSTEM_DATABASE); + if (req.databaseName() != StaticStrings::SystemDatabase) { + sysLvl = um->databaseAuthLevel(req.user(), StaticStrings::SystemDatabase, false); + } + bool isAdminUser = (sysLvl == auth::Level::RW); + if (!isAdminUser && ServerState::readOnly()) { + // in case we are in read-only mode, we need to re-check the original permissions + isAdminUser = um->databaseAuthLevel(req.user(), StaticStrings::SystemDatabase, true) == + auth::Level::RW; } return new VocbaseContext(req, vocbase, ExecContext::Type::Default, /*sysLevel*/ sysLvl, - /*dbLevel*/ dbLvl); + /*dbLevel*/ dbLvl, isAdminUser); } void VocbaseContext::forceSuperuser() { TRI_ASSERT(_type != ExecContext::Type::Internal || _user.empty()); - _type = ExecContext::Type::Internal; if (ServerState::readOnly()) { - _systemDbAuthLevel = auth::Level::RO; - _databaseAuthLevel = auth::Level::RO; + forceReadOnly(); } else { + _type = ExecContext::Type::Internal; _systemDbAuthLevel = auth::Level::RW; _databaseAuthLevel = auth::Level::RW; + _isAdminUser = true; } } diff --git a/arangod/RestServer/VocbaseContext.h b/arangod/RestServer/VocbaseContext.h index ecf448f481ed..b88c05f9ba11 100644 --- a/arangod/RestServer/VocbaseContext.h +++ b/arangod/RestServer/VocbaseContext.h @@ -52,7 +52,7 @@ class VocbaseContext : public arangodb::ExecContext { TRI_vocbase_t& _vocbase; VocbaseContext(GeneralRequest& req, TRI_vocbase_t& vocbase, ExecContext::Type type, - auth::Level systemLevel, auth::Level dbLevel); + auth::Level systemLevel, auth::Level dbLevel, bool isAdminUser); VocbaseContext(VocbaseContext const&) = delete; VocbaseContext& operator=(VocbaseContext const&) = delete; }; diff --git a/arangod/Utils/ExecContext.cpp b/arangod/Utils/ExecContext.cpp index ad044427efd1..9ab4932c278d 100644 --- a/arangod/Utils/ExecContext.cpp +++ b/arangod/Utils/ExecContext.cpp @@ -22,6 +22,8 @@ #include "ExecContext.h" +#include "Auth/UserManager.h" +#include "Basics/StaticStrings.h" #include "Cluster/ServerState.h" #include "GeneralServer/AuthenticationFeature.h" #include "VocBase/vocbase.h" @@ -31,7 +33,7 @@ using namespace arangodb; thread_local ExecContext const* ExecContext::CURRENT = nullptr; ExecContext ExecContext::Superuser(ExecContext::Type::Internal, /*name*/"", /*db*/"", - auth::Level::RW, auth::Level::RW); + auth::Level::RW, auth::Level::RW, true); /// Should always contain a reference to current user context /*static*/ ExecContext const& ExecContext::current() { @@ -42,17 +44,18 @@ ExecContext ExecContext::Superuser(ExecContext::Type::Internal, /*name*/"", /*db } ExecContext::ExecContext(ExecContext::Type type, std::string const& user, - std::string const& database, auth::Level systemLevel, auth::Level dbLevel) -: _user(user), - _database(database), - _type(type), - _canceled(false), - _systemDbAuthLevel(systemLevel), - _databaseAuthLevel(dbLevel) { - TRI_ASSERT(_systemDbAuthLevel != auth::Level::UNDEFINED); - TRI_ASSERT(_databaseAuthLevel != auth::Level::UNDEFINED); - } - + std::string const& database, auth::Level systemLevel, auth::Level dbLevel, + bool isAdminUser) + : _user(user), + _database(database), + _type(type), + _isAdminUser(isAdminUser), + _canceled(false), + _systemDbAuthLevel(systemLevel), + _databaseAuthLevel(dbLevel) { + TRI_ASSERT(_systemDbAuthLevel != auth::Level::UNDEFINED); + TRI_ASSERT(_databaseAuthLevel != auth::Level::UNDEFINED); +} bool ExecContext::isAuthEnabled() { AuthenticationFeature* af = AuthenticationFeature::instance(); @@ -69,6 +72,7 @@ std::unique_ptr ExecContext::create(std::string const& user, std::s TRI_ASSERT(af != nullptr); auth::Level dbLvl = auth::Level::RW; auth::Level sysLvl = auth::Level::RW; + bool isAdminUser = true; if (af->isActive()) { auth::UserManager* um = af->userManager(); TRI_ASSERT(um != nullptr); @@ -76,13 +80,17 @@ std::unique_ptr ExecContext::create(std::string const& user, std::s THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to find userManager instance"); } - dbLvl = sysLvl = um->databaseAuthLevel(user, dbname); - if (dbname != TRI_VOC_SYSTEM_DATABASE) { - sysLvl = um->databaseAuthLevel(user, TRI_VOC_SYSTEM_DATABASE); + dbLvl = sysLvl = um->databaseAuthLevel(user, dbname, false); + if (dbname != StaticStrings::SystemDatabase) { + sysLvl = um->databaseAuthLevel(user, StaticStrings::SystemDatabase, false); + } + isAdminUser = (sysLvl == auth::Level::RW); + if (!isAdminUser && ServerState::readOnly()) { + isAdminUser = um->databaseAuthLevel(user, StaticStrings::SystemDatabase, true) == auth::Level::RW; } } // we cannot use std::make_unique here, as ExecContext has a protected constructor - auto* ptr = new ExecContext(ExecContext::Type::Default, user, dbname, sysLvl, dbLvl); + auto* ptr = new ExecContext(ExecContext::Type::Default, user, dbname, sysLvl, dbLvl, isAdminUser); return std::unique_ptr(ptr); } @@ -120,14 +128,14 @@ auth::Level ExecContext::collectionAuthLevel(std::string const& dbname, if (!af->isActive()) { return auth::Level::RW; } - + if (coll.size() >= 5 && coll[0] == '_') { // _users, _queues, _frontend // handle fixed permissions here outside auth module. // TODO: move this block above, such that it takes effect // when authentication is disabled - if (dbname == TRI_VOC_SYSTEM_DATABASE && coll == TRI_COL_NAME_USERS) { + if (dbname == StaticStrings::SystemDatabase && coll == TRI_COL_NAME_USERS) { return auth::Level::NONE; } else if (coll == "_queues") { return auth::Level::RO; diff --git a/arangod/Utils/ExecContext.h b/arangod/Utils/ExecContext.h index 3dc0459d7140..cd69615aab79 100644 --- a/arangod/Utils/ExecContext.h +++ b/arangod/Utils/ExecContext.h @@ -46,7 +46,8 @@ class ExecContext : public RequestContext { enum class Type { Default, Internal }; ExecContext(ExecContext::Type type, std::string const& user, - std::string const& database, auth::Level systemLevel, auth::Level dbLevel); + std::string const& database, auth::Level systemLevel, auth::Level dbLevel, + bool isAdminUser); ExecContext(ExecContext const&) = delete; ExecContext(ExecContext&&) = delete; @@ -82,7 +83,7 @@ class ExecContext : public RequestContext { } /// @brief is allowed to manage users, create databases, ... - bool isAdminUser() const { return _systemDbAuthLevel == auth::Level::RW; } + bool isAdminUser() const { return _isAdminUser; } /// @brief should immediately cance this operation bool isCanceled() const { return _canceled; } @@ -109,6 +110,7 @@ class ExecContext : public RequestContext { bool canUseDatabase(auth::Level requested) const { return requested <= _databaseAuthLevel; } + /// @brief returns true if auth level is above or equal `requested` bool canUseDatabase(std::string const& db, auth::Level requested) const; @@ -132,15 +134,16 @@ class ExecContext : public RequestContext { std::string const _database; Type _type; + /// Flag if admin user access (not regarding cluster RO mode) + bool _isAdminUser; /// should be used to indicate a canceled request / thread bool _canceled; /// level of system database auth::Level _systemDbAuthLevel; /// level of current database auth::Level _databaseAuthLevel; - - private: + private: static ExecContext Superuser; static thread_local ExecContext const* CURRENT; }; diff --git a/arangod/V8Server/v8-vocbase.cpp b/arangod/V8Server/v8-vocbase.cpp index 78a7b4dcad6e..f8b5569a51c6 100644 --- a/arangod/V8Server/v8-vocbase.cpp +++ b/arangod/V8Server/v8-vocbase.cpp @@ -1682,7 +1682,7 @@ static void JS_DropDatabase(v8::FunctionCallbackInfo const& args) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE); } - if (!ExecContext::current().isAdminUser()) { + if (!ExecContext::current().isAdminUser() || (ServerState::readOnly() && !ExecContext::current().isSuperuser())) { events::DropDatabase("", TRI_ERROR_FORBIDDEN); TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN); } diff --git a/arangod/VocBase/Methods/Databases.cpp b/arangod/VocBase/Methods/Databases.cpp index e5a9872674f9..7aa5e7834352 100644 --- a/arangod/VocBase/Methods/Databases.cpp +++ b/arangod/VocBase/Methods/Databases.cpp @@ -285,7 +285,7 @@ arangodb::Result Databases::create(application_features::ApplicationServer& serv // Only admin users are permitted to create databases ExecContext const& exec = ExecContext::current(); - if (!exec.isAdminUser()) { + if (!exec.isAdminUser() || (ServerState::readOnly() && !exec.isSuperuser())) { events::CreateDatabase(dbName, TRI_ERROR_FORBIDDEN); return Result(TRI_ERROR_FORBIDDEN); } diff --git a/arangod/VocBase/vocbase.cpp b/arangod/VocBase/vocbase.cpp index 4840e5e8a3a0..45c4ba05287e 100644 --- a/arangod/VocBase/vocbase.cpp +++ b/arangod/VocBase/vocbase.cpp @@ -145,6 +145,10 @@ bool TRI_vocbase_t::markAsDropped() { // been marked as deleted return (oldValue % 2 == 0); } + +bool TRI_vocbase_t::isSystem() const { + return _info.getName() == StaticStrings::SystemDatabase; +} /// @brief signal the cleanup thread to wake up void TRI_vocbase_t::signalCleanup() { diff --git a/arangod/VocBase/vocbase.h b/arangod/VocBase/vocbase.h index 2cde02feff5b..e21fbba9bace 100644 --- a/arangod/VocBase/vocbase.h +++ b/arangod/VocBase/vocbase.h @@ -69,9 +69,6 @@ class StorageEngine; /// @brief predefined collection name for users constexpr auto TRI_COL_NAME_USERS = "_users"; -/// @brief name of the system database -constexpr auto TRI_VOC_SYSTEM_DATABASE = "_system"; - /// @brief maximal name length constexpr size_t TRI_COL_NAME_LENGTH = 256; @@ -264,7 +261,7 @@ struct TRI_vocbase_t { bool markAsDropped(); /// @brief returns whether the database is the system database - bool isSystem() const { return _info.getName() == TRI_VOC_SYSTEM_DATABASE; } + bool isSystem() const; /// @brief stop operations in this vocbase. must be called prior to /// shutdown to clean things up diff --git a/tests/IResearch/IResearchView-test.cpp b/tests/IResearch/IResearchView-test.cpp index 99b16c8d3b61..5c9b9c3cd8af 100644 --- a/tests/IResearch/IResearchView-test.cpp +++ b/tests/IResearch/IResearchView-test.cpp @@ -367,7 +367,7 @@ TEST_F(IResearchViewTest, test_defaults) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -885,7 +885,7 @@ TEST_F(IResearchViewTest, test_drop_with_link) { { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5161,7 +5161,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5233,7 +5233,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5272,7 +5272,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5336,7 +5336,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5404,7 +5404,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5467,7 +5467,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scopedExecContext(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5531,7 +5531,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scopedExecContext(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5599,7 +5599,7 @@ TEST_F(IResearchViewTest, test_update_overwrite) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scopedExecContext(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -6984,7 +6984,7 @@ TEST_F(IResearchViewTest, test_update_partial) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -7056,7 +7056,7 @@ TEST_F(IResearchViewTest, test_update_partial) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -7095,7 +7095,7 @@ TEST_F(IResearchViewTest, test_update_partial) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -7159,7 +7159,7 @@ TEST_F(IResearchViewTest, test_update_partial) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -7227,7 +7227,7 @@ TEST_F(IResearchViewTest, test_update_partial) { struct ExecContext: public arangodb::ExecContext { ExecContext(): arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", - arangodb::auth::Level::NONE, arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); diff --git a/tests/IResearch/IResearchViewCoordinator-test.cpp b/tests/IResearch/IResearchViewCoordinator-test.cpp index 5bcb0411bfe9..9ea363697c21 100644 --- a/tests/IResearch/IResearchViewCoordinator-test.cpp +++ b/tests/IResearch/IResearchViewCoordinator-test.cpp @@ -389,7 +389,7 @@ TEST_F(IResearchViewCoordinatorTest, test_defaults) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -767,7 +767,7 @@ TEST_F(IResearchViewCoordinatorTest, test_drop_with_link) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -2743,7 +2743,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_links_partial_add) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4189,7 +4189,7 @@ TEST_F(IResearchViewCoordinatorTest, test_drop_link) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4384,7 +4384,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_overwrite) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4492,7 +4492,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_overwrite) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4591,7 +4591,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_overwrite) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4736,7 +4736,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_overwrite) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -4899,7 +4899,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_overwrite) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5134,7 +5134,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_partial) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5242,7 +5242,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_partial) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope scope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5338,7 +5338,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_partial) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5490,7 +5490,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_partial) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -5653,7 +5653,7 @@ TEST_F(IResearchViewCoordinatorTest, test_update_partial) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); diff --git a/tests/Mocks/StorageEngineMock.cpp b/tests/Mocks/StorageEngineMock.cpp index f29f8cc55427..6bca3c162579 100644 --- a/tests/Mocks/StorageEngineMock.cpp +++ b/tests/Mocks/StorageEngineMock.cpp @@ -1626,7 +1626,7 @@ void StorageEngineMock::getDatabases(arangodb::velocypack::Builder& result) { arangodb::velocypack::Builder system; system.openObject(); - system.add("name", arangodb::velocypack::Value(TRI_VOC_SYSTEM_DATABASE)); + system.add("name", arangodb::velocypack::Value(arangodb::StaticStrings::SystemDatabase)); system.close(); // array expected diff --git a/tests/RestHandler/RestAnalyzerHandler-test.cpp b/tests/RestHandler/RestAnalyzerHandler-test.cpp index 49dbeba5c7a1..4b57e0f5e704 100644 --- a/tests/RestHandler/RestAnalyzerHandler-test.cpp +++ b/tests/RestHandler/RestAnalyzerHandler-test.cpp @@ -111,7 +111,7 @@ class RestAnalyzerHandlerTest ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope; // (&execContext); arangodb::aql::QueryRegistry queryRegistry; // required for UserManager::loadFromDB() diff --git a/tests/RestHandler/RestUsersHandler-test.cpp b/tests/RestHandler/RestUsersHandler-test.cpp index 86fdab49b2b3..15df31718272 100644 --- a/tests/RestHandler/RestUsersHandler-test.cpp +++ b/tests/RestHandler/RestUsersHandler-test.cpp @@ -196,7 +196,7 @@ TEST_F(RestUsersHandlerTest, test_collection_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, userName, "", arangodb::auth::Level::RW, - arangodb::auth::Level::NONE) { + arangodb::auth::Level::NONE, true) { } // ExecContext::isAdminUser() == true } execContext; arangodb::ExecContextScope execContextScope(&execContext); diff --git a/tests/RestHandler/RestViewHandler-test.cpp b/tests/RestHandler/RestViewHandler-test.cpp index 96e811ef421d..8e9d882b1c01 100644 --- a/tests/RestHandler/RestViewHandler-test.cpp +++ b/tests/RestHandler/RestViewHandler-test.cpp @@ -142,7 +142,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -251,7 +251,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -364,7 +364,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -525,7 +525,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -752,7 +752,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -901,7 +901,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -1052,7 +1052,7 @@ TEST_F(RestViewHandlerTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); diff --git a/tests/Transaction/Manager-test.cpp b/tests/Transaction/Manager-test.cpp index 288f7be88606..d8814d5315bb 100644 --- a/tests/Transaction/Manager-test.cpp +++ b/tests/Transaction/Manager-test.cpp @@ -435,7 +435,7 @@ TEST_F(TransactionManagerTest, permission_denied_readonly) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy", "testVocbase", arangodb::auth::Level::RO, - arangodb::auth::Level::RO) {} + arangodb::auth::Level::RO, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); @@ -465,7 +465,7 @@ TEST_F(TransactionManagerTest, permission_denied_forbidden) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy", "testVocbase", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); diff --git a/tests/Transaction/RestTransactionHandler-test.cpp b/tests/Transaction/RestTransactionHandler-test.cpp index f0f378deef05..d59c560e8800 100644 --- a/tests/Transaction/RestTransactionHandler-test.cpp +++ b/tests/Transaction/RestTransactionHandler-test.cpp @@ -315,7 +315,7 @@ TEST_F(RestTransactionHandlerTest, permission_denied_read_only) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy", "testVocbase", arangodb::auth::Level::RO, - arangodb::auth::Level::RO) {} + arangodb::auth::Level::RO, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); @@ -354,7 +354,7 @@ TEST_F(RestTransactionHandlerTest, permission_denied_forbidden) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy", "testVocbase", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); diff --git a/tests/V8Server/v8-analyzers-test.cpp b/tests/V8Server/v8-analyzers-test.cpp index 4417b8c4aa58..64fb0e065026 100644 --- a/tests/V8Server/v8-analyzers-test.cpp +++ b/tests/V8Server/v8-analyzers-test.cpp @@ -186,7 +186,7 @@ TEST_F(V8AnalyzerTest, test_instance_accessors) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto& authFeature = server.getFeature(); @@ -456,7 +456,7 @@ TEST_F(V8AnalyzerTest, test_manager_create) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto& authFeature = server.getFeature(); @@ -874,7 +874,7 @@ TEST_F(V8AnalyzerTest, test_manager_get) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto& authFeature = server.getFeature(); @@ -1258,7 +1258,7 @@ TEST_F(V8AnalyzerTest, test_manager_list) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto& authFeature = server.getFeature(); @@ -1576,7 +1576,7 @@ TEST_F(V8AnalyzerTest, test_manager_remove) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto& authFeature = server.getFeature(); diff --git a/tests/V8Server/v8-users-test.cpp b/tests/V8Server/v8-users-test.cpp index c0fd490365e0..8d0e044d5952 100644 --- a/tests/V8Server/v8-users-test.cpp +++ b/tests/V8Server/v8-users-test.cpp @@ -212,7 +212,7 @@ TEST_F(V8UsersTest, test_collection_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, userName, "", arangodb::auth::Level::RW, - arangodb::auth::Level::NONE) { + arangodb::auth::Level::NONE, true) { } // ExecContext::isAdminUser() == true } execContext; arangodb::ExecContextScope execContextScope(&execContext); diff --git a/tests/V8Server/v8-views-test.cpp b/tests/V8Server/v8-views-test.cpp index a83fa04c352e..ea813a2c6e7d 100644 --- a/tests/V8Server/v8-views-test.cpp +++ b/tests/V8Server/v8-views-test.cpp @@ -221,7 +221,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -344,7 +344,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -466,7 +466,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -592,7 +592,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -766,7 +766,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -941,7 +941,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -1084,7 +1084,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -1222,7 +1222,7 @@ TEST_F(V8ViewsTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); diff --git a/tests/VocBase/LogicalView-test.cpp b/tests/VocBase/LogicalView-test.cpp index 7ea6617a1191..77e4569e8119 100644 --- a/tests/VocBase/LogicalView-test.cpp +++ b/tests/VocBase/LogicalView-test.cpp @@ -173,7 +173,7 @@ TEST_F(LogicalViewTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "testVocbase", arangodb::auth::Level::NONE, - arangodb::auth::Level::NONE) {} + arangodb::auth::Level::NONE, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -191,7 +191,7 @@ TEST_F(LogicalViewTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "testVocbase", arangodb::auth::Level::NONE, - arangodb::auth::Level::RO) {} + arangodb::auth::Level::RO, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); @@ -210,7 +210,7 @@ TEST_F(LogicalViewTest, test_auth) { ExecContext() : arangodb::ExecContext(arangodb::ExecContext::Type::Default, "", "testVocbase", arangodb::auth::Level::NONE, - arangodb::auth::Level::RW) {} + arangodb::auth::Level::RW, false) {} } execContext; arangodb::ExecContextScope execContextScope(&execContext); auto* authFeature = arangodb::AuthenticationFeature::instance(); diff --git a/tests/js/client/authentication/admin-cluster-apis.js b/tests/js/client/authentication/admin-cluster-apis.js new file mode 100644 index 000000000000..b1a21d628fe1 --- /dev/null +++ b/tests/js/client/authentication/admin-cluster-apis.js @@ -0,0 +1,198 @@ +/*jshint globalstrict:false, strict:false */ +/*global fail, assertTrue, assertEqual */ + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test the authentication +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +const jsunity = require("jsunity"); +const arango = require("@arangodb").arango; +const db = require("internal").db; +const users = require("@arangodb/users"); +const request = require('@arangodb/request'); +const ERRORS = require('internal').errors; + +function AuthSuite() { + 'use strict'; + let baseUrl = function () { + return arango.getEndpoint().replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:'); + }; + + const user1 = 'hackers@arangodb.com'; + const user2 = 'noone@arangodb.com'; + let servers = []; + + let checkClusterHealth = function() { + let result = arango.GET('/_admin/cluster/health'); + assertTrue(result.hasOwnProperty('ClusterId'), result); + assertTrue(result.hasOwnProperty('Health'), result); + }; + + let checkClusterNodeVersion = function() { + // this will just redirect to a new endpoint + let result = arango.GET('/_admin/clusterNodeVersion?ServerID=' + servers[0]); + assertEqual(308, result.code); + + result = arango.GET('/_admin/cluster/nodeVersion?ServerID=' + servers[0]); + assertTrue(result.hasOwnProperty('server'), result); + assertTrue(result.hasOwnProperty('license'), result); + assertTrue(result.hasOwnProperty('version'), result); + }; + + let checkClusterNodeEngine = function() { + // this will just redirect to a new endpoint + let result = arango.GET('/_admin/clusterNodeEngine?ServerID=' + servers[0]); + assertEqual(308, result.code); + + result = arango.GET('/_admin/cluster/nodeEngine?ServerID=' + servers[0]); + assertTrue(result.hasOwnProperty('name'), result); + }; + + let checkClusterNodeStats = function() { + // this will just redirect to a new endpoint + let result = arango.GET('/_admin/clusterNodeStats?ServerID=' + servers[0]); + assertEqual(308, result.code); + + result = arango.GET('/_admin/cluster/nodeStatistics?ServerID=' + servers[0]); + assertTrue(result.hasOwnProperty('time'), result); + assertTrue(result.hasOwnProperty('system'), result); + }; + + return { + + setUpAll: function () { + servers = Object.keys(arango.GET('/_admin/cluster/health').Health).filter(function(s) { + return s.match(/^PRMR/); + }); + + assertTrue(servers.length > 0, servers); + + arango.reconnect(arango.getEndpoint(), '_system', "root", ""); + + try { + users.remove(user1); + } catch (err) { + } + try { + users.remove(user2); + } catch (err) { + } + + db._createDatabase('UnitTestsDatabase'); + db._useDatabase('UnitTestsDatabase'); + + users.save(user1, "foobar"); + users.save(user2, "foobar"); + users.grantDatabase(user1, 'UnitTestsDatabase'); + users.grantCollection(user1, 'UnitTestsDatabase', "*"); + users.grantDatabase(user2, 'UnitTestsDatabase', 'ro'); + users.reload(); + }, + + tearDownAll: function () { + arango.reconnect(arango.getEndpoint(), '_system', "root", ""); + try { + users.remove(user1); + } catch (err) { + } + try { + users.remove(user2); + } catch (err) { + } + + try { + db._dropDatabase('UnitTestsDatabase'); + } catch (err) { + } + }, + + testClusterHealthRoot: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', 'root', ''); + checkClusterHealth(); + }, + + testClusterHealthOtherDBRW: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user1, "foobar"); + checkClusterHealth(); + }, + + testClusterHealthOtherDBRO: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user2, "foobar"); + checkClusterHealth(); + }, + + testClusterNodeVersionRoot: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', 'root', ''); + checkClusterNodeVersion(); + }, + + testClusterNodeVersionDBRW: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user1, "foobar"); + checkClusterNodeVersion(); + }, + + testClusterNodeVersionDBRO: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user2, "foobar"); + checkClusterNodeVersion(); + }, + + testClusterNodeEngineRoot: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', 'root', ''); + checkClusterNodeEngine(); + }, + + testClusterNodeEngineOtherDBRW: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user1, "foobar"); + checkClusterNodeEngine(); + }, + + testClusterNodeEngineOtherDBRO: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user2, "foobar"); + checkClusterNodeEngine(); + }, + + testClusterNodeStatsRoot: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', 'root', ''); + checkClusterNodeStats(); + }, + + testClusterNodeStatsOtherDBRW: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user1, "foobar"); + checkClusterNodeStats(); + }, + + testClusterNodeStatsOtherDBRO: function () { + arango.reconnect(arango.getEndpoint(), 'UnitTestsDatabase', user2, "foobar"); + checkClusterNodeStats(); + }, + + }; +} + + +jsunity.run(AuthSuite); + +return jsunity.done(); diff --git a/tests/js/server/shell/shell-readonly-mode-spec.js b/tests/js/server/shell/shell-readonly-mode-spec.js index 2aa0bfd4957b..87366f9832b7 100644 --- a/tests/js/server/shell/shell-readonly-mode-spec.js +++ b/tests/js/server/shell/shell-readonly-mode-spec.js @@ -1,4 +1,4 @@ -/*global describe, it, ArangoAgency, after, afterEach, instanceInfo */ +/*global describe, it, ArangoAgency, after, afterEach, instanceInfo, fail */ //////////////////////////////////////////////////////////////////////////////// /// @brief cluster collection creation tests @@ -50,6 +50,21 @@ const waitForHeartbeat = function () { internal.wait(3 * heartbeatInterval, false); }; +const setReadOnlyAndGetDBServer = function() { + let servers = Object.keys(JSON.parse(download(endpoint + '/_admin/cluster/health').body).Health).filter(function(s) { + return s.match(/^PRMR/); + }); + + + let resp = download(endpoint + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), { + method: 'put', + }); + expect(resp.code).to.equal(200); + waitForHeartbeat(); + + return servers[0]; +}; + // this only tests the http api...there is a separate readonly test describe('Readonly mode api', function() { afterEach(function() { @@ -126,4 +141,72 @@ describe('Readonly mode api', function() { } }); }); + + it('can still access cluster/health API when readonly', function() { + if (!isCluster) { + return; + } + + let resp = download(endpoint + '/_admin/cluster/health'); + expect(resp.code).to.equal(200); + let body = JSON.parse(resp.body); + expect(body).to.have.property('ClusterId'); + expect(body).to.have.property('Health'); + }); + + it('can still access cluster/nodeVersion API when readonly', function() { + if (!isCluster) { + return; + } + + let server = setReadOnlyAndGetDBServer(); + let resp = download(endpoint + '/_admin/clusterNodeVersion?ServerID=' + server); + expect(resp.code).to.equal(308); + + resp = download(endpoint + '/_admin/cluster/nodeVersion?ServerID=' + server); + expect(resp.code).to.equal(200); + }); + + it('can still access cluster/nodeEngine API when readonly', function() { + if (!isCluster) { + return; + } + + let server = setReadOnlyAndGetDBServer(); + let resp = download(endpoint + '/_admin/clusterNodeEngine?ServerID=' + server); + expect(resp.code).to.equal(308); + + resp = download(endpoint + '/_admin/cluster/nodeEngine?ServerID=' + server); + expect(resp.code).to.equal(200); + }); + + it('can still access cluster/nodeStats API when readonly', function() { + if (!isCluster) { + return; + } + + let server = setReadOnlyAndGetDBServer(); + let resp = download(endpoint + '/_admin/clusterNodeStats?ServerID=' + server); + expect(resp.code).to.equal(308); + + resp = download(endpoint + '/_admin/cluster/nodeStatistics?ServerID=' + server); + expect(resp.code).to.equal(200); + }); + + it('cannot create a database when readonly', function() { + let resp = download(endpoint + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), { + method: 'put', + }); + expect(resp.code).to.equal(200); + waitForHeartbeat(); + + let body = JSON.parse(resp.body); + expect(body).to.have.property('mode', 'readonly'); + try { + db._createDatabase('UnitTestsDatabaseReadOnly'); + fail(); + } catch (err) { + expect(err.errorNum).to.equal(internal.errors.ERROR_FORBIDDEN.code); + } + }); });