1
0
Fork 0

added roles to users (#3354)

This commit is contained in:
Frank Celler 2017-10-01 21:00:39 +02:00 committed by GitHub
parent 206063c8fe
commit bd5e84a12f
12 changed files with 313 additions and 111 deletions

View File

@ -1,6 +1,13 @@
devel
-----
* usernames must not start with `:role:`, added new options:
--server.authentication-timeout
--ldap.roles-attribute-name
--ldap.roles-transformation
--ldap.roles-search
--ldap.superuser-role
* performance improvements for full collection scans and a few other operations
in MMFiles engine

View File

@ -221,12 +221,18 @@ The default value is *false*.
**Note**: this option is only available on platforms that support UNIX
domain sockets.
### Enable/disable authentication for system API requests only
@startDocuBlock serverAuthenticateSystemOnly
### Enable authentication cache timeout
`--server.authentication-timeout value`
Sets the cache timeout to *value* (in seconds). This is only necessary
if you use an external authentication system like LDAP.
### Enable/disable replication applier
`--database.replication-applier flag`

View File

@ -7,6 +7,8 @@ PostgreSQL, or other database systems.
User management is possible in the [web interface](../WebInterface/Users.md)
and in [arangosh](InArangosh.md) while logged on to the *\_system* database.
Note that the only usernames *must* not start with `:role:`.
Actions and Access Levels
-------------------------

View File

@ -61,15 +61,20 @@ rm -f $GENPATH
ln -s `pwd` $GENPATH
cd $GENPATH
if test -z "$CMAKE_BUILD_TYPE"; then
CMAKE_BUILD_TYPE=RelWithDebInfo
fi
echo "CONCURRENY: $concurrency"
echo "HOST: `hostname`"
echo "PWD: `pwd`"
echo "BUILD_TYPE: $CMAKE_BUILD_TYPE"
function configureBuild {
echo "`date +%T` configuring..."
CXXFLAGS=-fno-omit-frame-pointer \
cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_INSTALL_PREFIX=/ \
-DUSE_CATCH_TESTS=On \
-DDEBUG_SYNC_REPLICATION=On \

View File

@ -79,6 +79,10 @@ void AuthenticationFeature::collectOptions(
"enable or disable authentication for ALL client requests",
new BooleanParameter(&_active));
options->addOption("--server.authentication-timeout",
"timeout for the authentication cache (0 = undefinitely)",
new DoubleParameter(&_authenticationTimeout));
options->addOption(
"--server.authentication-system-only",
"use HTTP authentication only for requests to /_api and /_admin",

View File

@ -50,6 +50,7 @@ class AuthenticationFeature final
AuthInfo* _authInfo;
bool _authenticationUnixSockets;
bool _authenticationSystemOnly;
double _authenticationTimeout;
std::string _jwtSecretProgramOption;
bool _active;
@ -59,10 +60,12 @@ class AuthenticationFeature final
bool authenticationSystemOnly() const { return _authenticationSystemOnly; }
std::string jwtSecret() { return authInfo()->jwtSecret(); }
std::string generateNewJwtSecret();
bool hasUserdefinedJwt() { return !_jwtSecretProgramOption.empty(); }
bool hasUserdefinedJwt() const { return !_jwtSecretProgramOption.empty(); }
void setJwtSecret(std::string const& jwtSecret) {
authInfo()->setJwtSecret(jwtSecret);
}
double authenticationTimeout() const { return _authenticationTimeout; }
AuthInfo* authInfo();
AuthLevel canUseDatabase(std::string const& username,
std::string const& dbname);

View File

@ -43,19 +43,28 @@ class AuthenticationResult : public arangodb::Result {
AuthenticationResult(int errorNumber, AuthSource const& source)
: Result(errorNumber), _authSource(source) {}
AuthenticationResult(
std::unordered_map<std::string, std::string> const& permissions,
AuthSource const& source)
: Result(0), _authSource(source), _permissions(permissions) {}
std::unordered_set<std::string> roles, AuthSource const& source)
: Result(0),
_authSource(source),
_permissions(permissions),
_roles(roles) {}
public:
AuthSource source() const { return _authSource; }
std::unordered_map<std::string, std::string> permissions() const {
return _permissions;
}
std::unordered_set<std::string> roles() const { return _roles; }
protected:
AuthSource _authSource;
std::unordered_map<std::string, std::string> _permissions;
std::unordered_set<std::string> _roles;
};
class AuthenticationHandler {

View File

@ -103,6 +103,7 @@ bool AuthInfo::parseUsers(VPackSlice const& slice) {
<< s.get("user").copyString();
continue;
}
AuthUserEntry auth = AuthUserEntry::fromDocument(s);
// we also need to insert inactive users into the cache here
@ -121,7 +122,7 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(
LOG_TOPIC(DEBUG, arangodb::Logger::FIXME) << "system database is unknown";
THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL);
}
// we cannot set this execution context, otherwise the transaction
// will ask us again for permissions and we get a deadlock
ExecContext* oldExe = ExecContext::CURRENT;
@ -145,7 +146,7 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(
}
THROW_ARANGO_EXCEPTION_MESSAGE(queryResult.code,
"Error executing user query");
//return std::shared_ptr<VPackBuilder>();
// return std::shared_ptr<VPackBuilder>();
}
VPackSlice usersSlice = queryResult.result->slice();
@ -163,6 +164,7 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(
static VPackBuilder QueryUser(aql::QueryRegistry* queryRegistry,
std::string const& user) {
TRI_vocbase_t* vocbase = DatabaseFeature::DATABASE->systemDatabase();
if (vocbase == nullptr) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_FAILED, "_system db is unknown");
}
@ -172,7 +174,7 @@ static VPackBuilder QueryUser(aql::QueryRegistry* queryRegistry,
ExecContext* oldExe = ExecContext::CURRENT;
ExecContext::CURRENT = nullptr;
TRI_DEFER(ExecContext::CURRENT = oldExe);
std::string const queryStr("FOR u IN _users FILTER u.user == @name RETURN u");
auto emptyBuilder = std::make_shared<VPackBuilder>();
@ -191,22 +193,27 @@ static VPackBuilder QueryUser(aql::QueryRegistry* queryRegistry,
(queryResult.code == TRI_ERROR_QUERY_KILLED)) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED);
}
THROW_ARANGO_EXCEPTION_MESSAGE(queryResult.code,
"Error executing user query");
}
VPackSlice usersSlice = queryResult.result->slice();
if (usersSlice.isNone() || !usersSlice.isArray()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
if (usersSlice.length() == 0) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_USER_NOT_FOUND);
}
VPackSlice doc = usersSlice.at(0);
if (doc.isExternal()) {
doc = doc.resolveExternals();
}
return VPackBuilder(doc);
}
@ -227,12 +234,15 @@ static void ConvertLegacyFormat(VPackSlice doc, VPackBuilder& result) {
void AuthInfo::loadFromDB() {
TRI_ASSERT(_queryRegistry != nullptr);
TRI_ASSERT(ServerState::instance()->isSingleServerOrCoordinator());
auto role = ServerState::instance()->getRole();
if (role != ServerState::ROLE_SINGLE &&
role != ServerState::ROLE_COORDINATOR) {
_outdated = false;
return;
}
if (!_outdated) {
return;
}
@ -245,20 +255,24 @@ void AuthInfo::loadFromDB() {
}
WRITE_LOCKER(writeLocker, _authInfoLock);
try {
std::shared_ptr<VPackBuilder> builder = QueryAllUsers(_queryRegistry);
_authInfo.clear();
_authBasicCache.clear();
if (builder) {
VPackSlice usersSlice = builder->slice();
if (usersSlice.length() != 0) {
parseUsers(usersSlice);
}
}
_outdated = _authInfo.empty() == true;
_outdated = (_authInfo.empty() == true);
} catch (...) {
LOG_TOPIC(WARN, Logger::AUTHENTICATION)
<< "Exception when loading users from db";
<< "Exception when loading users from db";
_outdated = true;
}
}
@ -266,7 +280,7 @@ void AuthInfo::loadFromDB() {
// only call from the boostrap feature, must be sure to be the only one
void AuthInfo::createRootUser() {
loadFromDB();
MUTEX_LOCKER(locker, _loadFromDBLock);
WRITE_LOCKER(writeLocker, _authInfoLock);
auto it = _authInfo.find("root");
@ -275,7 +289,7 @@ void AuthInfo::createRootUser() {
return;
}
TRI_ASSERT(_authInfo.empty());
try {
// Attention:
// the root user needs to have a specific rights grant
@ -283,6 +297,7 @@ void AuthInfo::createRootUser() {
auto initDatabaseFeature =
application_features::ApplicationServer::getFeature<
InitDatabaseFeature>("InitDatabase");
TRI_ASSERT(initDatabaseFeature != nullptr);
AuthUserEntry entry = AuthUserEntry::newUser(
@ -308,7 +323,7 @@ Result AuthInfo::storeUserInternal(AuthUserEntry const& entry, bool replace) {
if (vocbase == nullptr) {
return Result(TRI_ERROR_INTERNAL);
}
// we cannot set this execution context, otherwise the transaction
// will ask us again for permissions and we get a deadlock
ExecContext* oldExe = ExecContext::CURRENT;
@ -355,7 +370,9 @@ Result AuthInfo::storeUserInternal(AuthUserEntry const& entry, bool replace) {
return res;
}
// ================= public ==================
// -----------------------------------------------------------------------------
// -- SECTION -- public
// -----------------------------------------------------------------------------
VPackBuilder AuthInfo::allUsers() {
// will query db directly, no need for _authInfoLock
@ -385,16 +402,20 @@ void AuthInfo::reloadAllUsers() {
// tell other coordinators to reload as well
AgencyComm agency;
int maxTries = 10;
while (maxTries-- > 0) {
AgencyCommResult commRes = agency.getValues("Sync/UserVersion");
if (!commRes.successful()) {
// Error in communication, note that value not found is not an error
LOG_TOPIC(TRACE, Logger::AUTHENTICATION)
<< "AuthInfo: no agency communication";
break;
}
VPackSlice oldVal = commRes.slice()[0].get(
{AgencyCommManager::path(), "Sync", "UserVersion"});
if (!oldVal.isInteger()) {
LOG_TOPIC(ERR, Logger::AUTHENTICATION)
<< "Sync/UserVersion is not a number";
@ -406,10 +427,12 @@ void AuthInfo::reloadAllUsers() {
commRes =
agency.casValue("Sync/UserVersion", oldVal, newVal.slice(), 0.0,
AgencyCommManager::CONNECTION_OPTIONS._requestTimeout);
if (commRes.successful()) {
return;
}
}
LOG_TOPIC(WARN, Logger::AUTHENTICATION)
<< "Sync/UserVersion could not be updated";
}
@ -419,6 +442,7 @@ Result AuthInfo::storeUser(bool replace, std::string const& user,
if (user.empty()) {
return TRI_ERROR_USER_INVALID_NAME;
}
loadFromDB();
WRITE_LOCKER(writeGuard, _authInfoLock);
@ -438,9 +462,11 @@ Result AuthInfo::storeUser(bool replace, std::string const& user,
}
Result r = storeUserInternal(entry, replace);
if (r.ok()) {
reloadAllUsers();
}
return r;
}
@ -450,7 +476,7 @@ static Result UpdateUser(VPackSlice const& user) {
if (vocbase == nullptr) {
return Result(TRI_ERROR_INTERNAL);
}
// we cannot set this execution context, otherwise the transaction
// will ask us again for permissions and we get a deadlock
ExecContext* oldExe = ExecContext::CURRENT;
@ -474,7 +500,8 @@ static Result UpdateUser(VPackSlice const& user) {
Result AuthInfo::enumerateUsers(
std::function<void(AuthUserEntry&)> const& func) {
loadFromDB();
// we require an consisten view on the user object
// we require an consistent view on the user object
{
WRITE_LOCKER(guard, _authInfoLock);
for (auto& it : _authInfo) {
@ -486,10 +513,12 @@ Result AuthInfo::enumerateUsers(
return r;
}
}
// must also clear the basic cache here because the secret may be
// invalid now if the password was changed
_authBasicCache.clear();
}
// we need to reload data after the next callback
reloadAllUsers();
return TRI_ERROR_NO_ERROR;
@ -500,20 +529,25 @@ Result AuthInfo::updateUser(std::string const& user,
if (user.empty()) {
return TRI_ERROR_USER_NOT_FOUND;
}
loadFromDB();
Result r;
{ // we require an consisten view on the user object
WRITE_LOCKER(guard, _authInfoLock);
auto it = _authInfo.find(user);
if (it == _authInfo.end()) {
return Result(TRI_ERROR_USER_NOT_FOUND);
}
TRI_ASSERT(!it->second.key().empty());
func(it->second);
VPackBuilder data = it->second.toVPackBuilder();
r = UpdateUser(data.slice());
// must also clear the basic cache here because the secret may be
// invalid now if the password was changed
_authBasicCache.clear();
}
@ -552,7 +586,7 @@ static Result RemoveUserInternal(AuthUserEntry const& entry) {
if (vocbase == nullptr) {
return Result(TRI_ERROR_INTERNAL);
}
// we cannot set this execution context, otherwise the transaction
// will ask us again for permissions and we get a deadlock
ExecContext* oldExe = ExecContext::CURRENT;
@ -613,19 +647,24 @@ Result AuthInfo::removeAllUsers() {
Result res;
{
WRITE_LOCKER(guard, _authInfoLock);
for (auto const& pair : _authInfo) {
res = RemoveUserInternal(pair.second);
if (!res.ok()) {
break;
}
}
{ // do not get into race conditions with loadFromDB
// do not get into race conditions with loadFromDB
{
MUTEX_LOCKER(locker, _loadFromDBLock);
_authInfo.clear();
_authBasicCache.clear();
_outdated = true;
}
}
reloadAllUsers();
return res;
}
@ -633,6 +672,7 @@ Result AuthInfo::removeAllUsers() {
VPackBuilder AuthInfo::getConfigData(std::string const& username) {
loadFromDB();
VPackBuilder bb = QueryUser(_queryRegistry, username);
return bb.isEmpty() ? bb : VPackBuilder(bb.slice().get("configData"));
}
@ -684,16 +724,23 @@ Result AuthInfo::setUserData(std::string const& user,
AuthResult AuthInfo::checkPassword(std::string const& username,
std::string const& password) {
AuthResult result(username);
if (StringUtils::isPrefix(username, ":role:")) {
return result;
}
loadFromDB();
READ_LOCKER(readLocker, _authInfoLock);
AuthResult result(username);
auto it = _authInfo.find(username);
if (it == _authInfo.end() || (it->second.source() == AuthSource::LDAP)) {
TRI_ASSERT(_authenticationHandler);
AuthenticationResult authResult =
_authenticationHandler->authenticate(username, password);
if (!authResult.ok()) {
return result;
}
@ -702,27 +749,33 @@ AuthResult AuthInfo::checkPassword(std::string const& username,
if (authResult.source() == AuthSource::LDAP) {
AuthUserEntry entry =
AuthUserEntry::newUser(username, password, AuthSource::LDAP);
entry.setRoles(authResult.roles());
// upgrade read-lock to a write-lock
readLocker.unlock();
WRITE_LOCKER(writeLocker, _authInfoLock);
it = _authInfo.find(username);
if (it != _authInfo.end()) {
it->second = entry;
} else {
auto r = _authInfo.emplace(username, entry);
if (!r.second) {
return result;
}
it = r.first;
}
AuthUserEntry const& auth = it->second;
if (auth.isActive()) {
result._authorized = auth.checkPassword(password);
}
return result;
} // AuthSource::LDAP
}
}
if (it != _authInfo.end()) {
@ -734,16 +787,34 @@ AuthResult AuthInfo::checkPassword(std::string const& username,
return result;
}
// public
#include <iostream>
AuthLevel AuthInfo::canUseDatabase(std::string const& username,
std::string const& dbname) {
loadFromDB();
READ_LOCKER(guard, _authInfoLock);
auto it = _authInfo.find(username);
if (it != _authInfo.end()) {
return it->second.databaseAuthLevel(dbname);
if (it == _authInfo.end()) {
return AuthLevel::NONE;
}
return AuthLevel::NONE;
auto const& entry = it->second;
AuthLevel level = entry.databaseAuthLevel(dbname);
for (auto const& role : entry.roles()) {
if (level == AuthLevel::RW) {
return level;
}
AuthLevel roleLevel = canUseDatabase(role, dbname);
if (level == AuthLevel::NONE) {
level = roleLevel;
}
}
return level;
}
AuthLevel AuthInfo::canUseCollection(std::string const& username,
@ -752,10 +823,27 @@ AuthLevel AuthInfo::canUseCollection(std::string const& username,
loadFromDB();
READ_LOCKER(guard, _authInfoLock);
auto it = _authInfo.find(username);
if (it != _authInfo.end()) {
return it->second.collectionAuthLevel(dbname, coll);
if (it == _authInfo.end()) {
return AuthLevel::NONE;
}
return AuthLevel::NONE;
auto const& entry = it->second;
AuthLevel level = entry.collectionAuthLevel(dbname, coll);
for (auto const& role : entry.roles()) {
if (level == AuthLevel::RW) {
return level;
}
AuthLevel roleLevel = canUseCollection(role, dbname, coll);
if (level == AuthLevel::NONE) {
level = roleLevel;
}
}
return level;
}
// public called from HttpCommTask.cpp and VstCommTask.cpp
@ -778,6 +866,7 @@ AuthResult AuthInfo::checkAuthentication(AuthenticationMethod authType,
// private
AuthResult AuthInfo::checkAuthenticationBasic(std::string const& secret) {
auto role = ServerState::instance()->getRole();
if (role != ServerState::ROLE_SINGLE &&
role != ServerState::ROLE_COORDINATOR) {
return AuthResult();
@ -786,13 +875,15 @@ AuthResult AuthInfo::checkAuthenticationBasic(std::string const& secret) {
{
READ_LOCKER(guard, _authInfoLock);
auto const& it = _authBasicCache.find(secret);
if (it != _authBasicCache.end()) {
if (it != _authBasicCache.end() && !it->second.expired()) {
return it->second;
}
}
std::string const up = StringUtils::decodeBase64(secret);
std::string::size_type n = up.find(':', 0);
if (n == std::string::npos || n == 0 || n + 1 > up.size()) {
LOG_TOPIC(TRACE, arangodb::Logger::AUTHENTICATION)
<< "invalid authentication data found, cannot extract "
@ -804,6 +895,13 @@ AuthResult AuthInfo::checkAuthenticationBasic(std::string const& secret) {
std::string password = up.substr(n + 1);
AuthResult result = checkPassword(username, password);
double timeout = AuthenticationFeature::INSTANCE->authenticationTimeout();
if (0 < timeout) {
result.setExpiry(TRI_microtime() + timeout);
}
{
WRITE_LOCKER(guard, _authInfoLock);

View File

@ -47,11 +47,15 @@ class AuthResult {
AuthResult() : _authorized(false) {}
explicit AuthResult(std::string const& username)
: _username(username), _authorized(false) {}
: _username(username), _authorized(false), _expiry(0) {}
void setExpiry(double expiry) { _expiry = expiry; }
bool expired() { return _expiry != 0 && _expiry < TRI_microtime(); }
public:
std::string _username;
/// User exists and password was checked
bool _authorized;
bool _authorized; // User exists and password was checked
double _expiry;
};
class AuthJwtResult : public AuthResult {
@ -79,7 +83,7 @@ class AuthInfo {
/// Trigger eventual reload, user facing API call
void reloadAllUsers();
/// Create the root user with a default password, will fail if the user
/// already exists. Only ever call if you can guarantee to be in charge
void createRootUser();
@ -92,7 +96,7 @@ class AuthInfo {
Result updateUser(std::string const& username,
std::function<void(AuthUserEntry&)> const&);
Result accessUser(std::string const& username,
std::function<void(AuthUserEntry const&)> const&);
std::function<void(AuthUserEntry const&)> const&);
velocypack::Builder serializeUser(std::string const& user);
Result removeUser(std::string const& user);
Result removeAllUsers();

View File

@ -168,6 +168,73 @@ AuthUserEntry AuthUserEntry::newUser(std::string const& user,
return entry;
}
void AuthUserEntry::fromDocumentRoles(AuthUserEntry& entry,
VPackSlice const& rolesSlice) {
for (auto const& it : VPackArrayIterator(rolesSlice)) {
if (it.isString()) {
std::string const role = it.copyString();
entry._roles.insert(role);
}
}
}
void AuthUserEntry::fromDocumentDatabases(AuthUserEntry& entry,
VPackSlice const& databasesSlice,
VPackSlice const& userSlice) {
for (auto const& obj : VPackObjectIterator(databasesSlice)) {
std::string const dbName = obj.key.copyString();
if (obj.value.isObject()) {
AuthLevel databaseAuth = AuthLevel::NONE;
auto const permissionsSlice = obj.value.get("permissions");
if (permissionsSlice.isObject()) {
databaseAuth = AuthLevelFromSlice(permissionsSlice);
}
try {
entry.grantDatabase(dbName, databaseAuth);
} catch (arangodb::basics::Exception const& e) {
LOG_TOPIC(DEBUG, Logger::AUTHORIZATION) << e.message();
}
VPackSlice collectionsSlice = obj.value.get("collections");
if (collectionsSlice.isObject()) {
for (auto const& collection : VPackObjectIterator(collectionsSlice)) {
std::string const cName = collection.key.copyString();
auto const permissionsSlice = collection.value.get("permissions");
if (permissionsSlice.isObject()) {
try {
entry.grantCollection(dbName, cName,
AuthLevelFromSlice(permissionsSlice));
} catch (arangodb::basics::Exception const& e) {
LOG_TOPIC(DEBUG, Logger::AUTHORIZATION) << e.message();
}
}
}
}
} else {
LOG_TOPIC(DEBUG, arangodb::Logger::CONFIG)
<< "updating deprecated access rights struct for user '"
<< userSlice.copyString() << "'";
VPackValueLength length;
char const* value = obj.value.getString(length);
if (TRI_CaseEqualString(value, "rw", 2)) {
entry.grantDatabase(dbName, AuthLevel::RW);
entry.grantCollection(dbName, "*", AuthLevel::RW);
} else if (TRI_CaseEqualString(value, "ro", 2)) {
entry.grantDatabase(dbName, AuthLevel::RO);
entry.grantCollection(dbName, "*", AuthLevel::RO);
}
}
}
}
AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
if (slice.isNone() || !slice.isObject()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_BAD_PARAMETER);
@ -175,6 +242,7 @@ AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
VPackSlice const keySlice =
transaction::helpers::extractKeyFromDocument(slice);
if (!keySlice.isString()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"cannot extract key");
@ -182,6 +250,7 @@ AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
// extract "user" attribute
VPackSlice const userSlice = slice.get("user");
if (!userSlice.isString()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"cannot extract username");
@ -195,6 +264,7 @@ AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
}
VPackSlice const simpleSlice = authDataSlice.get("simple");
if (!simpleSlice.isObject()) {
LOG_TOPIC(DEBUG, arangodb::Logger::FIXME) << "cannot extract simple";
return AuthUserEntry();
@ -213,6 +283,7 @@ AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
// extract "active" attribute
VPackSlice const activeSlice = authDataSlice.get("active");
if (!activeSlice.isBoolean()) {
LOG_TOPIC(DEBUG, arangodb::Logger::FIXME) << "cannot extract active flag";
return AuthUserEntry();
@ -229,56 +300,17 @@ AuthUserEntry AuthUserEntry::fromDocument(VPackSlice const& slice) {
// extract "databases" attribute
VPackSlice const databasesSlice = slice.get("databases");
if (databasesSlice.isObject()) {
for (auto const& obj : VPackObjectIterator(databasesSlice)) {
std::string const dbName = obj.key.copyString();
fromDocumentDatabases(entry, databasesSlice, userSlice);
}
if (obj.value.isObject()) {
AuthLevel databaseAuth = AuthLevel::NONE;
// extract "roles" attribute
VPackSlice const rolesSlice = slice.get("roles");
auto const permissionsSlice = obj.value.get("permissions");
if (permissionsSlice.isObject()) {
databaseAuth = AuthLevelFromSlice(permissionsSlice);
}
try {
entry.grantDatabase(dbName, databaseAuth);
} catch(arangodb::basics::Exception const& e) {
LOG_TOPIC(DEBUG, Logger::AUTHORIZATION) << e.message();
}
VPackSlice collectionsSlice = obj.value.get("collections");
if (collectionsSlice.isObject()) {
for (auto const& collection : VPackObjectIterator(collectionsSlice)) {
std::string const cName = collection.key.copyString();
auto const permissionsSlice = collection.value.get("permissions");
if (permissionsSlice.isObject()) {
try {
entry.grantCollection(dbName, cName,
AuthLevelFromSlice(permissionsSlice));
} catch(arangodb::basics::Exception const& e) {
LOG_TOPIC(DEBUG, Logger::AUTHORIZATION) << e.message();
}
} // if
} // for
} // if
} else {
LOG_TOPIC(DEBUG, arangodb::Logger::CONFIG)
<< "updating deprecated access rights struct for user '"
<< userSlice.copyString() << "'";
VPackValueLength length;
char const* value = obj.value.getString(length);
if (TRI_CaseEqualString(value, "rw", 2)) {
entry.grantDatabase(dbName, AuthLevel::RW);
entry.grantCollection(dbName, "*", AuthLevel::RW);
} else if (TRI_CaseEqualString(value, "ro", 2)) {
entry.grantDatabase(dbName, AuthLevel::RO);
entry.grantCollection(dbName, "*", AuthLevel::RO);
}
}
} // for
} // if
if (rolesSlice.isArray()) {
fromDocumentRoles(entry, rolesSlice);
}
// ensure the root user always has the right to change permissions
if (entry._username == "root") {
@ -317,13 +349,16 @@ VPackBuilder AuthUserEntry::toVPackBuilder() const {
VPackBuilder builder;
VPackObjectBuilder o(&builder, true);
if (!_key.empty()) {
builder.add(StaticStrings::KeyString, VPackValue(_key));
}
builder.add("user", VPackValue(_username));
AddSource(builder, _source);
{ // authData sub-object
// authData sub-object
{
VPackObjectBuilder o2(&builder, "authData", true);
builder.add("active", VPackValue(_active));
if (_source == AuthSource::COLLECTION) {
@ -333,17 +368,23 @@ VPackBuilder AuthUserEntry::toVPackBuilder() const {
builder.add("method", VPackValue(_passwordMethod));
}
}
{ // databases sub-object
VPackObjectBuilder o2(&builder, "databases", true);
for (auto const& dbCtxPair : _dbAccess) {
VPackObjectBuilder o3(&builder, dbCtxPair.first, true);
{ // permissions
// permissions
{
VPackObjectBuilder o4(&builder, "permissions", true);
AuthLevel lvl = dbCtxPair.second._databaseAuthLevel;
AddAuthLevel(builder, lvl);
}
{ // collections
// collections
{
VPackObjectBuilder o4(&builder, "collections", true);
for (auto const& colAccessPair : dbCtxPair.second._collectionAccess) {
VPackObjectBuilder o4(&builder, colAccessPair.first, true);
VPackObjectBuilder o5(&builder, "permissions", true);
@ -361,6 +402,7 @@ void AuthUserEntry::grantDatabase(std::string const& dbname, AuthLevel level) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"Cannot set rights for empty db name");
}
if (_username == "root" && dbname == StaticStrings::SystemDatabase &&
level != AuthLevel::RW) {
THROW_ARANGO_EXCEPTION_MESSAGE(
@ -368,12 +410,14 @@ void AuthUserEntry::grantDatabase(std::string const& dbname, AuthLevel level) {
}
auto it = _dbAccess.find(dbname);
if (it != _dbAccess.end()) {
it->second._databaseAuthLevel = level;
} else {
// grantDatabase is not supposed to change any rights on the collection
// level
// code which relies on the old behaviour will need to be adjusted
// grantDatabase is not supposed to change any rights on the
// collection level code which relies on the old behaviour
// will need to be adjusted
_dbAccess.emplace(
dbname,
DBAuthContext(level, std::unordered_map<std::string, AuthLevel>()));
@ -396,13 +440,14 @@ void AuthUserEntry::grantCollection(std::string const& dbname,
std::string const& coll,
AuthLevel const level) {
if (dbname.empty() || coll.empty()) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
THROW_ARANGO_EXCEPTION_MESSAGE(
TRI_ERROR_BAD_PARAMETER,
"Cannot set rights for empty db / collection name");
} else if (coll[0] == '_') {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER,
"Cannot set rights for system collections");
"Cannot set rights for system collections");
} else if (_username == "root" && dbname == StaticStrings::SystemDatabase &&
coll == "*" && level != AuthLevel::RW) {
coll == "*" && level != AuthLevel::RW) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN,
"Cannot lower access level of 'root' to "
" a system collection");
@ -418,9 +463,10 @@ void AuthUserEntry::grantCollection(std::string const& dbname,
if (it != _dbAccess.end()) {
dbLevel = it->second._databaseAuthLevel;
}
_dbAccess.emplace(dbname, DBAuthContext(
dbLevel, std::unordered_map<std::string, AuthLevel>(
{{coll, level}})));
_dbAccess.emplace(
dbname,
DBAuthContext(dbLevel, std::unordered_map<std::string, AuthLevel>(
{{coll, level}})));
}
}
@ -517,7 +563,7 @@ bool AuthUserEntry::hasSpecificCollection(
AuthLevel AuthUserEntry::DBAuthContext::collectionAuthLevel(
std::string const& collectionName, bool& notFound) const {
std::unordered_map<std::string, AuthLevel>::const_iterator pair =
_collectionAccess.find(collectionName);
_collectionAccess.find(collectionName);
if (pair != _collectionAccess.end()) {
return pair->second;
}

View File

@ -32,11 +32,11 @@
namespace arangodb {
/// This class represents a 'user' entry. It contains structures
/// to store the access levels for databases and collections.
/// The user object must be serialized via `toVPackBuilder()` and
/// written to the _users collection after modifying it.
///
// This class represents a 'user' entry. It contains structures to
// store the access levels for databases and collections. The user
// object must be serialized via `toVPackBuilder()` and written to the
// _users collection after modifying it.
class AuthUserEntry {
friend class AuthInfo;
@ -45,6 +45,12 @@ class AuthUserEntry {
AuthSource source);
static AuthUserEntry fromDocument(velocypack::Slice const&);
private:
static void fromDocumentRoles(AuthUserEntry&, velocypack::Slice const&);
static void fromDocumentDatabases(AuthUserEntry&,
velocypack::Slice const& databases,
velocypack::Slice const& user);
public:
std::string const& key() const { return _key; }
std::string const& username() const { return _username; }
@ -61,25 +67,32 @@ class AuthUserEntry {
void setActive(bool active) { _active = active; }
/// grant specific access rights for db. The default "*"
/// is also a valid database name
std::unordered_set<std::string> roles () const { return _roles; }
void setRoles(std::unordered_set<std::string> roles) { _roles = roles; }
// grant specific access rights for db. The default "*" is also a
// valid database name
void grantDatabase(std::string const& dbname, AuthLevel level);
/// Removes the entry.
// Removes the entry.
void removeDatabase(std::string const& dbname);
/// Grant collection rights, "*" is a valid parameter for dbname and
/// collection.
/// The combination of "*"/"*" is automatically used for the root user
// Grant collection rights, "*" is a valid parameter for dbname and
// collection. The combination of "*"/"*" is automatically used for
// the root
void grantCollection(std::string const& dbname, std::string const& collection,
AuthLevel level);
void removeCollection(std::string const& dbname,
std::string const& collection);
/// Resolve the access level for this database. Might fall back to
/// the special '*' entry if the specific database is not found
// Resolve the access level for this database. Might fall back to
// the special '*' entry if the specific database is not found
AuthLevel databaseAuthLevel(std::string const& dbname) const;
/// Resolve rights for the specified collection. Falls back to the
/// special '*' entry if either the database or collection is not
/// found.
// Resolve rights for the specified collection. Falls back to the
// special '*' entry if either the database or collection is not
// found.
AuthLevel collectionAuthLevel(std::string const& dbname,
std::string const& collectionName) const;
@ -94,12 +107,13 @@ class AuthUserEntry {
DBAuthContext(AuthLevel dbLvl,
std::unordered_map<std::string, AuthLevel> const& coll)
: _databaseAuthLevel(dbLvl), _collectionAccess(coll) {}
DBAuthContext(AuthLevel dbLvl,
std::unordered_map<std::string, AuthLevel>&& coll)
: _databaseAuthLevel(dbLvl), _collectionAccess(std::move(coll)) {}
AuthLevel collectionAuthLevel(std::string const& collectionName, bool& notFound) const;
AuthLevel collectionAuthLevel(std::string const& collectionName,
bool& notFound) const;
public:
AuthLevel _databaseAuthLevel;
@ -116,6 +130,7 @@ class AuthUserEntry {
std::string _passwordSalt;
std::string _passwordHash;
std::unordered_map<std::string, DBAuthContext> _dbAccess;
std::unordered_set<std::string> _roles;
};
}

View File

@ -28,6 +28,7 @@
#ifdef USE_ENTERPRISE
#include "Enterprise/Audit/AuditFeature.h"
#include "Enterprise/Ldap/LdapFeature.h"
#endif
using namespace arangodb;
@ -73,6 +74,8 @@ LogTopic Logger::V8("v8", LogLevel::WARN);
LogTopic Logger::VIEWS("views", LogLevel::FATAL);
#ifdef USE_ENTERPRISE
LogTopic LdapFeature::LDAP("ldap", LogLevel::INFO);
LogTopic AuditFeature::AUDIT_AUTHENTICATION("audit-authentication", LogLevel::INFO);
LogTopic AuditFeature::AUDIT_DATABASE("audit-database", LogLevel::INFO);
LogTopic AuditFeature::AUDIT_COLLECTION("audit-collection", LogLevel::INFO);