1
0
Fork 0
arangodb/arangod/V8Server/v8-users.cpp

529 lines
18 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// 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 Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#include "v8-users.h"
#include "ApplicationFeatures/ApplicationServer.h"
#include "Basics/VelocyPackHelper.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "RestServer/DatabaseFeature.h"
#include "RestServer/FeatureCacheFeature.h"
#include "Utils/ExecContext.h"
#include "V8/v8-conv.h"
#include "V8/v8-globals.h"
#include "V8/v8-utils.h"
#include "V8/v8-vpack.h"
#include "V8/v8-vpack.h"
#include "V8Server/v8-externals.h"
#include "V8Server/v8-vocbase.h"
#include "V8Server/v8-vocbaseprivate.h"
#include "V8Server/v8-vocindex.h"
#include "VocBase/AuthInfo.h"
#include "VocBase/LogicalCollection.h"
#include <velocypack/Builder.h>
#include <velocypack/HexDump.h>
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::rest;
static bool IsAdminUser() {
if (ExecContext::CURRENT != nullptr) {
return ExecContext::CURRENT->isAdminUser();
}
return true;
}
/// check ExecContext if system use
static bool CanAccessUser(std::string const& user) {
if (ExecContext::CURRENT != nullptr) {
return IsAdminUser()|| user == ExecContext::CURRENT->user();
}
return true;
}
void StoreUser(v8::FunctionCallbackInfo<v8::Value> const& args, bool replace) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 1) {
TRI_V8_THROW_EXCEPTION_USAGE(
"save(username, password[, active, userData])");
} else if (!args[0]->IsString()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_USER_INVALID_NAME);
}
std::string username = TRI_ObjectToString(args[0]);
if (!CanAccessUser(username)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
std::string pass = args.Length() > 1 && args[1]->IsString()
? TRI_ObjectToString(args[1])
: "";
bool active = true;
if (args.Length() >= 3 && args[2]->IsBoolean()) {
active = TRI_ObjectToBoolean(args[2]);
}
VPackBuilder extras;
if (args.Length() >= 4) {
int r = TRI_V8ToVPackSimple(isolate, extras, args[3]);
if (r != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(r);
}
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
Result r = authentication->authInfo()->storeUser(replace, username, pass,
active);
if (r.fail()) {
TRI_V8_THROW_EXCEPTION(r);
}
if (!extras.isEmpty()) {
authentication->authInfo()->setUserData(username, extras.slice());
}
VPackBuilder result = authentication->authInfo()->serializeUser(username);
if (!result.isEmpty()) {
TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice()));
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_SaveUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
StoreUser(args, false);
}
static void JS_ReplaceUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
StoreUser(args, true);
}
static void JS_UpdateUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE(
"update(username[, password, active, userData])");
}
std::string username = TRI_ObjectToString(args[0]);
if (!CanAccessUser(username)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
VPackBuilder extras;
if (args.Length() >= 4) {
int r = TRI_V8ToVPackSimple(isolate, extras, args[3]);
if (r != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(r);
}
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
authentication->authInfo()->updateUser(username, [&](AuthUserEntry& entry) {
if (args.Length() > 1 && args[1]->IsString()) {
entry.updatePassword(TRI_ObjectToString(args[1]));
}
if (args.Length() > 2 && args[2]->IsBoolean()) {
entry.setActive(TRI_ObjectToBoolean(args[2]));
}
});
if (!extras.isEmpty()) {
authentication->authInfo()->setUserData(username, extras.slice());
}
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}
static void JS_RemoveUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("remove(username)");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
std::string username = TRI_ObjectToString(args[0]);
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
Result r = authentication->authInfo()->removeUser(username);
if (!r.ok()) {
TRI_V8_THROW_EXCEPTION(r);
}
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}
static void JS_GetUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("document(username)");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string username = TRI_ObjectToString(args[0]);
VPackBuilder result = authentication->authInfo()->serializeUser(username);
if (!result.isEmpty()) {
TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice()));
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_ReloadAuthData(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() > 0) {
TRI_V8_THROW_EXCEPTION_USAGE("reload()");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
authentication->authInfo()->outdate();
if (ServerState::instance()->isCoordinator()) {
authentication->authInfo()->reloadAllUsers();
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_GrantDatabase(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("grantDatabase(username, database, type)");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string username = TRI_ObjectToString(args[0]);
std::string db = TRI_ObjectToString(args[1]);
AuthLevel lvl = AuthLevel::RW;
if (args.Length() >= 3) {
std::string type = TRI_ObjectToString(args[2]);
lvl = convertToAuthLevel(type);
}
Result r = authentication->authInfo()->updateUser(
username, [&](AuthUserEntry& entry) { entry.grantDatabase(db, lvl); });
if (!r.ok()) {
TRI_V8_THROW_EXCEPTION(r);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_RevokeDatabase(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("revokeDatabase(username, database)");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string username = TRI_ObjectToString(args[0]);
std::string db = TRI_ObjectToString(args[1]);
Result r = authentication->authInfo()->updateUser(
username,
[&](AuthUserEntry& entry) { entry.removeDatabase(db); });
if (!r.ok()) {
TRI_V8_THROW_EXCEPTION(r);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_GrantCollection(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString() ||
!args[2]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("grantCollection(username, db, coll[, type])");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string username = TRI_ObjectToString(args[0]);
std::string db = TRI_ObjectToString(args[1]);
std::string coll = TRI_ObjectToString(args[2]);
AuthLevel lvl = AuthLevel::RW;
if (args.Length() >= 4) {
std::string type = TRI_ObjectToString(args[3]);
lvl = convertToAuthLevel(type);
}
Result r = authentication->authInfo()->updateUser(
username,
[&](AuthUserEntry& entry) { entry.grantCollection(db, coll, lvl); });
if (!r.ok()) {
TRI_V8_THROW_EXCEPTION(r);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_RevokeCollection(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 3 || !args[0]->IsString() || !args[1]->IsString() ||
!args[2]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("revokeCollection(username, db, coll)");
}
if (!IsAdminUser()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string username = TRI_ObjectToString(args[0]);
std::string db = TRI_ObjectToString(args[1]);
std::string coll = TRI_ObjectToString(args[2]);
Result r = authentication->authInfo()->updateUser(
username, [&](AuthUserEntry& entry) {
entry.removeCollection(db, coll);
});
if (!r.ok()) {
TRI_V8_THROW_EXCEPTION(r);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
// create/update (value != null) or delete (value == null)
static void JS_UpdateConfigData(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) {
TRI_V8_THROW_EXCEPTION_USAGE("updateConfigData(username, key[, value])");
}
std::string username = TRI_ObjectToString(args[0]);
if (!CanAccessUser(username)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
std::string key = TRI_ObjectToString(args[1]);
VPackBuilder merge;
if (args.Length() > 2) {
VPackBuilder value;
int res = TRI_V8ToVPackSimple(isolate, value, args[2]);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
merge.add(key, value.slice());
} else {
merge.add(key, VPackSlice::nullSlice());
}
VPackBuilder old = authentication->authInfo()->getConfigData(username);
VPackBuilder updated =
VelocyPackHelper::merge(old.slice(), merge.slice(), true, true);
authentication->authInfo()->setConfigData(username, updated.slice());
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_GetConfigData(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsString() ||
(args.Length() > 1 && !args[1]->IsString())) {
TRI_V8_THROW_EXCEPTION_USAGE("configData(username[, key])");
}
std::string username = TRI_ObjectToString(args[0]);
if (!CanAccessUser(username)) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN);
}
auto authentication =
FeatureCacheFeature::instance()->authenticationFeature();
VPackBuilder config = authentication->authInfo()->getConfigData(username);
if (!config.isEmpty()) {
TRI_V8_RETURN(TRI_VPackToV8(isolate, config.slice()));
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_GetPermission(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() > 3 || args.Length() == 0 || !args[0]->IsString() ||
(args.Length() > 1 && !args[1]->IsString()) ||
(args.Length() > 2 && !args[2]->IsString())) {
TRI_V8_THROW_EXCEPTION_USAGE("permission(username[, database, collection])");
}
auto authentication = AuthenticationFeature::INSTANCE;
std::string username = TRI_ObjectToString(isolate, args[0]);
if (args.Length() > 1) {
std::string dbname = TRI_ObjectToString(isolate, args[1]);
AuthLevel lvl;
if (args.Length() == 3) {
std::string collection = TRI_ObjectToString(isolate, args[2]);
lvl = authentication->authInfo()->canUseCollection(username, dbname, collection);
} else {
lvl = authentication->authInfo()->canUseDatabase(username, dbname);
}
if (lvl == AuthLevel::RO) {
TRI_V8_RETURN(TRI_V8_ASCII_STRING(isolate, "ro"));
} else if (lvl == AuthLevel::RW) {
TRI_V8_RETURN(TRI_V8_ASCII_STRING(isolate, "rw"));
}
TRI_V8_RETURN(TRI_V8_ASCII_STRING(isolate, "none"));
} else {
v8::Handle<v8::Object> result = v8::Object::New(isolate);
DatabaseFeature::DATABASE->enumerateDatabases([&](TRI_vocbase_t* vocbase) {
AuthLevel lvl =
authentication->authInfo()->canUseDatabase(username, vocbase->name());
if (lvl != AuthLevel::NONE) {
std::string str = AuthLevel::RO == lvl ? "ro" : "rw";
result->ForceSet(TRI_V8_STD_STRING(isolate, vocbase->name()),
TRI_V8_STD_STRING(isolate, str));
}
});
TRI_V8_RETURN(result);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_AuthIsActive(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
auto authentication = AuthenticationFeature::INSTANCE;
if (authentication->isActive()) {
TRI_V8_RETURN_TRUE();
} else {
TRI_V8_RETURN_FALSE();
}
TRI_V8_TRY_CATCH_END
}
static void JS_CurrentUser(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() != 0 ) {
TRI_V8_THROW_EXCEPTION_USAGE("currentUser()");
}
if (ExecContext::CURRENT != nullptr) {
TRI_V8_RETURN(TRI_V8_STD_STRING(isolate, ExecContext::CURRENT->user()));
}
TRI_V8_RETURN_NULL();
TRI_V8_TRY_CATCH_END
}
void TRI_InitV8Users(v8::Handle<v8::Context> context, TRI_vocbase_t* vocbase,
TRI_v8_global_t* v8g, v8::Isolate* isolate) {
v8::Handle<v8::ObjectTemplate> rt;
v8::Handle<v8::FunctionTemplate> ft;
ft = v8::FunctionTemplate::New(isolate);
ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoUsers"));
rt = ft->InstanceTemplate();
rt->SetInternalFieldCount(2);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "save"), JS_SaveUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "replace"),
JS_ReplaceUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "update"),
JS_UpdateUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "remove"),
JS_RemoveUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "document"),
JS_GetUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "reload"),
JS_ReloadAuthData);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "grantDatabase"),
JS_GrantDatabase);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "revokeDatabase"),
JS_RevokeDatabase);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "grantCollection"),
JS_GrantCollection);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "revokeCollection"),
JS_RevokeCollection);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "updateConfigData"),
JS_UpdateConfigData);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "configData"),
JS_GetConfigData);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "permission"),
JS_GetPermission);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "currentUser"),
JS_CurrentUser);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "isAuthActive"),
JS_AuthIsActive);
v8g->UsersTempl.Reset(isolate, rt);
ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoUsersCtor"));
TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoUsersCtor"),
ft->GetFunction(), true);
// register the global object
v8::Handle<v8::Object> aa = rt->NewInstance();
if (!aa.IsEmpty()) {
TRI_AddGlobalVariableVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoUsers"),
aa);
}
}