mirror of https://gitee.com/bigwinds/arangodb
Feature/allow access to configured 🔐 in readonly mode (#3698)
* Special method to access configured access levels as opposed to only getting the effective access level * Add integration test * Allow grants on collection level
This commit is contained in:
parent
7b80deb5cc
commit
75e1bf31cd
|
@ -130,22 +130,41 @@ RestStatus RestUsersHandler::getRequest(AuthInfo* authInfo) {
|
|||
} else if (suffixes.size() == 3) {
|
||||
//_api/user/<user>/database/<dbname>
|
||||
// return specific database
|
||||
AuthLevel lvl = authInfo->canUseDatabase(user, suffixes[2]);
|
||||
bool configured = false;
|
||||
std::string const& param = _request->value("configured", configured);
|
||||
if (configured) {
|
||||
configured = StringUtils::boolean(param);
|
||||
}
|
||||
AuthLevel lvl;
|
||||
if (configured) {
|
||||
lvl = authInfo->configuredDatabaseAuthLevel(user, suffixes[2]);
|
||||
} else {
|
||||
// return effective user rights
|
||||
lvl = authInfo->canUseDatabase(user, suffixes[2]);
|
||||
}
|
||||
VPackBuilder data;
|
||||
data.add(VPackValue(convertFromAuthLevel(lvl)));
|
||||
generateOk(ResponseCode::OK, data.slice());
|
||||
|
||||
} else if (suffixes.size() == 4) {
|
||||
bool configured = false;
|
||||
std::string const& param = _request->value("configured", configured);
|
||||
if (configured) {
|
||||
configured = StringUtils::boolean(param);
|
||||
}
|
||||
//_api/user/<user>/database/<dbname>/<collection>
|
||||
AuthLevel lvl =
|
||||
authInfo->canUseCollection(user, suffixes[2], suffixes[3]);
|
||||
AuthLevel lvl;
|
||||
if (configured) {
|
||||
lvl = authInfo->configuredCollectionAuthLevel(user, suffixes[2], suffixes[3]);
|
||||
} else {
|
||||
// return effective user rights
|
||||
lvl = authInfo->canUseCollection(user, suffixes[2], suffixes[3]);
|
||||
}
|
||||
VPackBuilder data;
|
||||
data.add(VPackValue(convertFromAuthLevel(lvl)));
|
||||
generateOk(ResponseCode::OK, data.slice());
|
||||
} else {
|
||||
generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
|
||||
}
|
||||
|
||||
} else if (suffixes[1] == "config") {
|
||||
//_api/user/<user>//config
|
||||
VPackBuilder data = authInfo->getConfigData(user);
|
||||
|
|
|
@ -58,7 +58,7 @@ class DatabaseManagerThread : public Thread {
|
|||
}
|
||||
};
|
||||
|
||||
class DatabaseFeature final : public application_features::ApplicationFeature {
|
||||
class DatabaseFeature : public application_features::ApplicationFeature {
|
||||
friend class DatabaseManagerThread;
|
||||
|
||||
public:
|
||||
|
|
|
@ -858,30 +858,11 @@ AuthResult AuthInfo::checkPassword(std::string const& username,
|
|||
return result;
|
||||
}
|
||||
|
||||
AuthLevel AuthInfo::canUseDatabase(std::string const& username,
|
||||
std::string const& dbname) {
|
||||
loadFromDB();
|
||||
AuthLevel level;
|
||||
{
|
||||
READ_LOCKER(guard, _authInfoLock);
|
||||
level = canUseDatabaseInternal(username, dbname, 0);
|
||||
}
|
||||
|
||||
static_assert(AuthLevel::RO < AuthLevel::RW, "ro < rw");
|
||||
if (level > AuthLevel::RO && !ServerState::writeOpsEnabled()) {
|
||||
// no write operations allowed on this server at all
|
||||
LOG_TOPIC(TRACE, Logger::FIXME) << "downgrading user rights";
|
||||
return AuthLevel::RO;
|
||||
}
|
||||
// return actual level
|
||||
return level;
|
||||
}
|
||||
|
||||
// worker function for canUseDatabase
|
||||
// worker function for configuredDatabaseAuthLevel
|
||||
// must only be called with the read-lock on _authInfoLock being held
|
||||
AuthLevel AuthInfo::canUseDatabaseInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
size_t depth) const {
|
||||
AuthLevel AuthInfo::configuredDatabaseAuthLevelInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
size_t depth) const {
|
||||
auto it = _authInfo.find(username);
|
||||
|
||||
if (it == _authInfo.end()) {
|
||||
|
@ -902,7 +883,7 @@ AuthLevel AuthInfo::canUseDatabaseInternal(std::string const& username,
|
|||
// recurse into function, but only one level deep.
|
||||
// this allows us to avoid endless recursion without major overhead
|
||||
if (depth == 0) {
|
||||
AuthLevel roleLevel = canUseDatabaseInternal(role, dbname, depth + 1);
|
||||
AuthLevel roleLevel = configuredDatabaseAuthLevelInternal(role, dbname, depth + 1);
|
||||
|
||||
if (level == AuthLevel::NONE) {
|
||||
// use the permission of the role we just found
|
||||
|
@ -914,43 +895,35 @@ AuthLevel AuthInfo::canUseDatabaseInternal(std::string const& username,
|
|||
return level;
|
||||
}
|
||||
|
||||
AuthLevel AuthInfo::canUseCollection(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll) {
|
||||
if (coll.empty()) {
|
||||
// no collection name given
|
||||
return AuthLevel::NONE;
|
||||
}
|
||||
|
||||
if (coll[0] >= '0' && coll[0] <= '9') {
|
||||
// lookup by collection id
|
||||
// translate numeric collection id into collection name
|
||||
return canUseCollectionInternal(
|
||||
username, dbname,
|
||||
DatabaseFeature::DATABASE->translateCollectionName(dbname, coll));
|
||||
}
|
||||
|
||||
// lookup by collection name
|
||||
return canUseCollectionInternal(username, dbname, coll);
|
||||
AuthLevel AuthInfo::configuredDatabaseAuthLevel(std::string const& username,
|
||||
std::string const& dbname) {
|
||||
loadFromDB();
|
||||
READ_LOCKER(guard, _authInfoLock);
|
||||
return configuredDatabaseAuthLevelInternal(username, dbname, 0);
|
||||
}
|
||||
|
||||
// internal method called by canUseCollection
|
||||
AuthLevel AuthInfo::canUseDatabase(std::string const& username,
|
||||
std::string const& dbname) {
|
||||
AuthLevel level = configuredDatabaseAuthLevel(username, dbname);
|
||||
static_assert(AuthLevel::RO < AuthLevel::RW, "ro < rw");
|
||||
if (level > AuthLevel::RO && !ServerState::writeOpsEnabled()) {
|
||||
return AuthLevel::RO;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
// internal method called by configuredCollectionAuthLevel
|
||||
// asserts that collection name is non-empty and already translated
|
||||
// from collection id to name
|
||||
AuthLevel AuthInfo::canUseCollectionInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll) {
|
||||
if (coll.empty()) {
|
||||
// no collection name given
|
||||
return AuthLevel::NONE;
|
||||
}
|
||||
AuthLevel AuthInfo::configuredCollectionAuthLevelInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll,
|
||||
size_t depth) const {
|
||||
|
||||
// we must have got a non-empty collection name when we get here
|
||||
TRI_ASSERT(coll[0] < '0' || coll[0] > '9');
|
||||
|
||||
loadFromDB();
|
||||
READ_LOCKER(guard, _authInfoLock);
|
||||
auto it = _authInfo.find(username);
|
||||
|
||||
if (it == _authInfo.end()) {
|
||||
return AuthLevel::NONE;
|
||||
}
|
||||
|
@ -961,25 +934,60 @@ AuthLevel AuthInfo::canUseCollectionInternal(std::string const& username,
|
|||
#ifdef USE_ENTERPRISE
|
||||
for (auto const& role : entry.roles()) {
|
||||
if (level == AuthLevel::RW) {
|
||||
// we already have highest permission
|
||||
return level;
|
||||
}
|
||||
|
||||
AuthLevel roleLevel = canUseCollection(role, dbname, coll);
|
||||
// recurse into function, but only one level deep.
|
||||
// this allows us to avoid endless recursion without major overhead
|
||||
if (depth == 0) {
|
||||
AuthLevel roleLevel = configuredCollectionAuthLevelInternal(role, dbname, coll, depth + 1);
|
||||
|
||||
if (level == AuthLevel::NONE) {
|
||||
level = roleLevel;
|
||||
if (level == AuthLevel::NONE) {
|
||||
// use the permission of the role we just found
|
||||
level = roleLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return level;
|
||||
}
|
||||
|
||||
AuthLevel AuthInfo::configuredCollectionAuthLevel(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string coll) {
|
||||
if (coll.empty()) {
|
||||
// no collection name given
|
||||
return AuthLevel::NONE;
|
||||
}
|
||||
if (coll[0] >= '0' && coll[0] <= '9') {
|
||||
coll = DatabaseFeature::DATABASE->translateCollectionName(dbname, coll);
|
||||
}
|
||||
|
||||
loadFromDB();
|
||||
READ_LOCKER(guard, _authInfoLock);
|
||||
|
||||
return configuredCollectionAuthLevelInternal(username, dbname, coll, 0);
|
||||
}
|
||||
|
||||
AuthLevel AuthInfo::canUseCollection(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll) {
|
||||
if (coll.empty()) {
|
||||
// no collection name given
|
||||
return AuthLevel::NONE;
|
||||
}
|
||||
|
||||
AuthLevel level = configuredCollectionAuthLevel(username, dbname, coll);
|
||||
static_assert(AuthLevel::RO < AuthLevel::RW, "ro < rw");
|
||||
if (level > AuthLevel::RO && !ServerState::writeOpsEnabled()) {
|
||||
LOG_TOPIC(ERR, Logger::FIXME) << "downgrading user rights";
|
||||
return AuthLevel::RO;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public called from HttpCommTask.cpp and VstCommTask.cpp
|
||||
// should only lock if required, otherwise we will serialize all
|
||||
// requests whether we need to or not
|
||||
|
@ -1287,3 +1295,9 @@ std::string AuthInfo::generateJwt(VPackBuilder const& payload) {
|
|||
}
|
||||
return generateRawJwt(bodyBuilder);
|
||||
}
|
||||
|
||||
void AuthInfo::setAuthInfo(AuthUserEntryMap const& newMap) {
|
||||
WRITE_LOCKER(writeLocker, _authInfoLock);
|
||||
_authInfo = newMap;
|
||||
_outdated = false;
|
||||
}
|
||||
|
|
|
@ -68,7 +68,10 @@ class AuthJwtResult : public AuthResult {
|
|||
|
||||
class AuthenticationHandler;
|
||||
|
||||
typedef std::unordered_map<std::string, AuthUserEntry> AuthUserEntryMap;
|
||||
|
||||
class AuthInfo {
|
||||
|
||||
public:
|
||||
explicit AuthInfo(std::unique_ptr<AuthenticationHandler>&&);
|
||||
~AuthInfo();
|
||||
|
@ -112,7 +115,11 @@ class AuthInfo {
|
|||
|
||||
AuthResult checkAuthentication(arangodb::rest::AuthenticationMethod authType,
|
||||
std::string const& secret);
|
||||
|
||||
AuthLevel configuredDatabaseAuthLevel(std::string const& username,
|
||||
std::string const& dbname);
|
||||
AuthLevel configuredCollectionAuthLevel(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string coll);
|
||||
AuthLevel canUseDatabase(std::string const& username,
|
||||
std::string const& dbname);
|
||||
AuthLevel canUseCollection(std::string const& username,
|
||||
|
@ -124,18 +131,21 @@ class AuthInfo {
|
|||
std::string generateJwt(VPackBuilder const&);
|
||||
std::string generateRawJwt(VPackBuilder const&);
|
||||
|
||||
void setAuthInfo(AuthUserEntryMap const& userEntryMap);
|
||||
|
||||
private:
|
||||
// worker function for canUseDatabase
|
||||
// must only be called with the read-lock on _authInfoLock being held
|
||||
AuthLevel canUseDatabaseInternal(std::string const& username,
|
||||
AuthLevel configuredDatabaseAuthLevelInternal(std::string const& username,
|
||||
std::string const& dbname, size_t depth) const;
|
||||
|
||||
// internal method called by canUseCollection
|
||||
// asserts that collection name is non-empty and already translated
|
||||
// from collection id to name
|
||||
AuthLevel canUseCollectionInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll);
|
||||
AuthLevel configuredCollectionAuthLevelInternal(std::string const& username,
|
||||
std::string const& dbname,
|
||||
std::string const& coll,
|
||||
size_t depth) const;
|
||||
void loadFromDB();
|
||||
bool parseUsers(velocypack::Slice const& slice);
|
||||
Result storeUserInternal(AuthUserEntry const& user, bool replace);
|
||||
|
@ -154,7 +164,7 @@ class AuthInfo {
|
|||
Mutex _loadFromDBLock;
|
||||
std::atomic<bool> _outdated;
|
||||
|
||||
std::unordered_map<std::string, AuthUserEntry> _authInfo;
|
||||
AuthUserEntryMap _authInfo;
|
||||
std::unordered_map<std::string, arangodb::AuthResult> _authBasicCache;
|
||||
arangodb::basics::LruCache<std::string, arangodb::AuthJwtResult>
|
||||
_authJwtCache;
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/* jshint globalstrict:false, strict:false, maxlen: 5000 */
|
||||
/* global describe, after, afterEach, before, it */
|
||||
'use strict';
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
// / DISCLAIMER
|
||||
// /
|
||||
// / Copyright 2017 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
|
||||
// /
|
||||
// / @author Andreas Streichardt
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const request = require('@arangodb/request');
|
||||
const users = require('@arangodb/users');
|
||||
const db = require('@arangodb').db;
|
||||
|
||||
describe('Grants', function() {
|
||||
before(function() {
|
||||
db._create('grants');
|
||||
});
|
||||
afterEach(function() {
|
||||
users.remove('hans');
|
||||
let resp = request.put({
|
||||
url: '/_admin/server/mode',
|
||||
body: {'mode': 'default'},
|
||||
json: true,
|
||||
});
|
||||
expect(resp.statusCode).to.equal(200);
|
||||
});
|
||||
after(function() {
|
||||
db._drop('grants');
|
||||
// wait for readonly mode to reset
|
||||
require('internal').wait(5.0);
|
||||
});
|
||||
it('should show the effective rights for a user', function() {
|
||||
users.save('hans');
|
||||
users.grantDatabase('hans', '_system', 'rw');
|
||||
let resp = request.get({
|
||||
url: '/_api/user/hans/database/_system',
|
||||
json: true,
|
||||
});
|
||||
expect(JSON.parse(resp.body)).to.have.property('result', 'rw');
|
||||
});
|
||||
|
||||
it('should show the effective rights when readonly mode is on', function() {
|
||||
users.save('hans');
|
||||
users.grantDatabase('hans', '_system', 'rw');
|
||||
|
||||
let resp;
|
||||
resp = request.put({
|
||||
url: '/_admin/server/mode',
|
||||
body: {'mode': 'readonly'},
|
||||
json: true,
|
||||
});
|
||||
resp = request.get({
|
||||
url: '/_api/user/hans/database/_system',
|
||||
json: true,
|
||||
});
|
||||
expect(JSON.parse(resp.body)).to.have.property('result', 'ro');
|
||||
});
|
||||
|
||||
it('should show the configured rights when readonly mode is on and configured is requested', function() {
|
||||
users.save('hans');
|
||||
users.grantDatabase('hans', '_system', 'rw');
|
||||
|
||||
let resp;
|
||||
resp = request.put({
|
||||
url: '/_admin/server/mode',
|
||||
body: {'mode': 'readonly'},
|
||||
json: true,
|
||||
});
|
||||
resp = request.get({
|
||||
url: '/_api/user/hans/database/_system?configured=true',
|
||||
json: true,
|
||||
});
|
||||
expect(JSON.parse(resp.body)).to.have.property('result', 'rw');
|
||||
});
|
||||
|
||||
it('should show the effective rights when readonly mode is on', function() {
|
||||
users.save('hans');
|
||||
users.grantDatabase('hans', '_system', 'rw');
|
||||
users.grantCollection('hans', '_system', 'grants');
|
||||
|
||||
let resp;
|
||||
resp = request.put({
|
||||
url: '/_admin/server/mode',
|
||||
body: {'mode': 'readonly'},
|
||||
json: true,
|
||||
});
|
||||
resp = request.get({
|
||||
url: '/_api/user/hans/database/_system/grants',
|
||||
json: true,
|
||||
});
|
||||
expect(JSON.parse(resp.body)).to.have.property('result', 'ro');
|
||||
});
|
||||
|
||||
it('should show the configured rights when readonly mode is on and configured is requested', function() {
|
||||
users.save('hans');
|
||||
users.grantDatabase('hans', '_system', 'rw');
|
||||
users.grantCollection('hans', '_system', 'grants');
|
||||
|
||||
let resp;
|
||||
resp = request.put({
|
||||
url: '/_admin/server/mode',
|
||||
body: {'mode': 'readonly'},
|
||||
json: true,
|
||||
});
|
||||
resp = request.get({
|
||||
url: '/_api/user/hans/database/_system/grants?configured=true',
|
||||
json: true,
|
||||
});
|
||||
expect(JSON.parse(resp.body)).to.have.property('result', 'rw');
|
||||
});
|
||||
});
|
|
@ -61,6 +61,7 @@ add_executable(
|
|||
RocksDBEngine/IndexEstimatorTest.cpp
|
||||
RocksDBEngine/TypeConversionTest.cpp
|
||||
SimpleHttpClient/CommunicatorTest.cpp
|
||||
VocBase/AuthInfoTest.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite for CuckooFilter based index selectivity estimator
|
||||
///
|
||||
/// @file
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2017 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
|
||||
///
|
||||
/// @author Andreas Streichardt
|
||||
/// @author Copyright 2017, ArangoDB GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "fakeit.hpp"
|
||||
|
||||
#include "Aql/QueryRegistry.h"
|
||||
#include "Cluster/ServerState.h"
|
||||
#include "RestServer/DatabaseFeature.h"
|
||||
#include "VocBase/AuthInfo.h"
|
||||
#include "VocBase/AuthUserEntry.h"
|
||||
|
||||
using namespace fakeit;
|
||||
using namespace arangodb;
|
||||
using namespace arangodb::aql;
|
||||
|
||||
namespace arangodb {
|
||||
namespace tests {
|
||||
namespace auth_info_test {
|
||||
|
||||
class TestAuthenticationHandler: public AuthenticationHandler {
|
||||
public:
|
||||
TestAuthenticationHandler() {}
|
||||
AuthenticationResult authenticate(std::string const& username,
|
||||
std::string const& password) {
|
||||
|
||||
std::unordered_map<std::string, AuthLevel> permissions {};
|
||||
std::unordered_set<std::string> roles {};
|
||||
AuthSource source = AuthSource::COLLECTION;
|
||||
AuthenticationResult result(permissions, roles, source);
|
||||
return result;
|
||||
}
|
||||
virtual ~TestAuthenticationHandler() {}
|
||||
};
|
||||
|
||||
class TestQueryRegistry: public QueryRegistry {
|
||||
public:
|
||||
TestQueryRegistry() {};
|
||||
virtual ~TestQueryRegistry() {}
|
||||
};
|
||||
|
||||
class TestDatabaseFeature: public DatabaseFeature {
|
||||
public:
|
||||
TestDatabaseFeature(application_features::ApplicationServer* server): DatabaseFeature(server) {};
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE("🥑🔐 AuthInfo", "[authentication]") {
|
||||
auto authHandler = std::make_unique<TestAuthenticationHandler>();
|
||||
TestQueryRegistry queryRegistry;
|
||||
|
||||
auto state = ServerState::instance();
|
||||
state->setRole(ServerState::ROLE_SINGLE);
|
||||
|
||||
Mock<DatabaseFeature> databaseFeatureMock;
|
||||
DatabaseFeature &databaseFeature = databaseFeatureMock.get();
|
||||
DatabaseFeature::DATABASE = &databaseFeature;
|
||||
|
||||
AuthInfo authInfo(std::move(authHandler));
|
||||
authInfo.setQueryRegistry(&queryRegistry);
|
||||
|
||||
SECTION("An unknown user will have no access") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.canUseDatabase("test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::NONE);
|
||||
}
|
||||
|
||||
SECTION("Granting RW access on database * will grant access to all databases") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
auto testUser = AuthUserEntry::newUser("test", "test", AuthSource::COLLECTION);
|
||||
testUser.grantDatabase("*", AuthLevel::RW);
|
||||
userEntryMap.emplace("test", testUser);
|
||||
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.canUseDatabase("test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::RW);
|
||||
}
|
||||
|
||||
SECTION("Setting ServerState to readonly will make all users effective RO users") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
auto testUser = AuthUserEntry::newUser("test", "test", AuthSource::COLLECTION);
|
||||
testUser.grantDatabase("*", AuthLevel::RW);
|
||||
userEntryMap.emplace("test", testUser);
|
||||
|
||||
state->setServerMode(ServerState::Mode::READ_ONLY);
|
||||
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.canUseDatabase("test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::RO);
|
||||
}
|
||||
|
||||
SECTION("In readonly mode the configured access level will still be accessible") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
auto testUser = AuthUserEntry::newUser("test", "test", AuthSource::COLLECTION);
|
||||
testUser.grantDatabase("*", AuthLevel::RW);
|
||||
userEntryMap.emplace("test", testUser);
|
||||
|
||||
state->setServerMode(ServerState::Mode::READ_ONLY);
|
||||
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.configuredDatabaseAuthLevel("test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::RW);
|
||||
}
|
||||
|
||||
SECTION("Setting ServerState to readonly will make all users effective RO users (collection level)") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
auto testUser = AuthUserEntry::newUser("test", "test", AuthSource::COLLECTION);
|
||||
testUser.grantDatabase("*", AuthLevel::RW);
|
||||
testUser.grantCollection("test", "test", AuthLevel::RW);
|
||||
userEntryMap.emplace("test", testUser);
|
||||
|
||||
state->setServerMode(ServerState::Mode::READ_ONLY);
|
||||
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.canUseCollection("test", "test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::RO);
|
||||
}
|
||||
|
||||
SECTION("In readonly mode the configured access level will still be accessible (collection level)") {
|
||||
AuthUserEntryMap userEntryMap;
|
||||
auto testUser = AuthUserEntry::newUser("test", "test", AuthSource::COLLECTION);
|
||||
testUser.grantDatabase("*", AuthLevel::RW);
|
||||
testUser.grantCollection("test", "test", AuthLevel::RW);
|
||||
userEntryMap.emplace("test", testUser);
|
||||
|
||||
state->setServerMode(ServerState::Mode::READ_ONLY);
|
||||
|
||||
authInfo.setAuthInfo(userEntryMap);
|
||||
AuthLevel authLevel = authInfo.configuredCollectionAuthLevel("test", "test", "test");
|
||||
REQUIRE(authLevel == AuthLevel::RW);
|
||||
}
|
||||
|
||||
state->setServerMode(ServerState::Mode::DEFAULT);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue