1
0
Fork 0
arangodb/arangod/RestHandler/RestUsersHandler.cpp

623 lines
20 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 "RestUsersHandler.h"
#include "Basics/VelocyPackHelper.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Rest/HttpRequest.h"
#include "Rest/Version.h"
#include "RestServer/DatabaseFeature.h"
#include "Utils/CollectionNameResolver.h"
#include "Utils/ExecContext.h"
#include "VocBase/LogicalCollection.h"
#include "VocBase/Methods/Collections.h"
#include "VocBase/vocbase.h"
#include <velocypack/Builder.h>
#include <velocypack/Collection.h>
#include <velocypack/velocypack-aliases.h>
namespace {
////////////////////////////////////////////////////////////////////////////////
/// @return a collection exists in database or a wildcard was specified
////////////////////////////////////////////////////////////////////////////////
arangodb::Result existsCollection(
std::string const& database, std::string const& collection
) {
auto* databaseFeature = arangodb::application_features::ApplicationServer::lookupFeature<
arangodb::DatabaseFeature
>("Database");
if (!databaseFeature) {
return arangodb::Result(
TRI_ERROR_INTERNAL, "failure to find feature 'Database'"
);
}
static const std::string wildcard("*");
if (wildcard == database) {
return arangodb::Result(); // wildcard always matches
}
auto* vocbase = databaseFeature->lookupDatabase(database);
if (!vocbase) {
return arangodb::Result(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
if (wildcard == collection) {
return arangodb::Result(); // wildcard always matches
}
return !arangodb::CollectionNameResolver(*vocbase).getCollection(collection)
? arangodb::Result(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND)
: arangodb::Result()
;
}
}
using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::rest;
RestUsersHandler::RestUsersHandler(GeneralRequest* request,
GeneralResponse* response)
: RestBaseHandler(request, response) {}
RestStatus RestUsersHandler::execute() {
RequestType const type = _request->requestType();
AuthenticationFeature* af = AuthenticationFeature::instance();
if (af == nullptr || af->userManager() == nullptr) {
// nullptr may happens during shutdown, or on Agency
generateError(ResponseCode::BAD, TRI_ERROR_NOT_IMPLEMENTED);
return RestStatus::DONE;
}
switch (type) {
case RequestType::GET:
return getRequest(af->userManager());
case RequestType::POST:
return postRequest(af->userManager());
case RequestType::PUT:
return putRequest(af->userManager());
case RequestType::PATCH:
return patchRequest(af->userManager());
case RequestType::DELETE_REQ:
return deleteRequest(af->userManager());
default:
generateError(ResponseCode::BAD, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED);
return RestStatus::DONE;
}
}
bool RestUsersHandler::isAdminUser() const {
if (!ExecContext::isAuthEnabled()) {
return true;
} else if (ExecContext::CURRENT != nullptr) {
return ExecContext::CURRENT->isAdminUser();
}
return false;
}
bool RestUsersHandler::canAccessUser(std::string const& user) const {
if (_request->authenticated() && user == _request->user()) {
return true;
}
return isAdminUser();
}
/// helper to generate a compliant response for individual user requests
void RestUsersHandler::generateUserResult(rest::ResponseCode code,
VPackBuilder const& doc) {
VPackBuilder b;
b(VPackValue(VPackValueType::Object))(StaticStrings::Error, VPackValue(false))(
StaticStrings::Code, VPackValue((int)code))();
b = VPackCollection::merge(doc.slice(), b.slice(), false);
generateResult(code, b.slice());
}
RestStatus RestUsersHandler::getRequest(auth::UserManager* um) {
std::vector<std::string> suffixes = _request->decodedSuffixes();
if (suffixes.empty()) {
if (isAdminUser()) {
VPackBuilder users = um->allUsers();
generateOk(ResponseCode::OK, users.slice());
} else {
generateError(ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN);
}
} else if (suffixes.size() == 1) {
std::string const& user = suffixes[0];
if (canAccessUser(user)) {
VPackBuilder doc = um->serializeUser(user);
generateUserResult(ResponseCode::OK, doc);
} else {
generateError(ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN);
}
} else {
std::string const& user = suffixes[0];
if (!canAccessUser(user)) {
generateError(ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN);
return RestStatus::DONE;
}
if (suffixes[1] == "database") {
if (suffixes.size() == 2) {
//_api/user/<user>/database?full=<true/false>
bool full = false;
std::string const& param = _request->value("full", full);
if (full) {
full = StringUtils::boolean(param);
}
generateDatabaseResult(um, user, full);
} else if (suffixes.size() == 3) {
//_api/user/<user>/database/<dbname>
// return specific database
bool configured = _request->parsedValue("configured", false);
auth::Level lvl = um->databaseAuthLevel(user, suffixes[2], configured);
VPackBuilder data;
data.add(VPackValue(convertFromAuthLevel(lvl)));
generateOk(ResponseCode::OK, data.slice());
} else if (suffixes.size() == 4) {
bool configured = _request->parsedValue("configured", false);
//_api/user/<user>/database/<dbname>/<collection>
auth::Level lvl = um->collectionAuthLevel(user, suffixes[2],
suffixes[3], configured);
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 (only used by WebUI)
Result r;
r = um->accessUser(user, [&](auth::User const& u) {
VPackSlice resp = u.configData();
if (suffixes.size() == 3 && resp.isObject()) {
resp = resp.get(suffixes[2]);
}
generateOk(ResponseCode::OK,
resp.isNone() ? VPackSlice::nullSlice() : resp);
return TRI_ERROR_NO_ERROR;
});
if (r.fail()) {
generateError(r);
}
} else {
generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
}
return RestStatus::DONE;
}
/// generate response for /_api/user/database?full=true/false
void RestUsersHandler::generateDatabaseResult(auth::UserManager* um,
std::string const& username,
bool full) {
// return list of databases
VPackBuilder data;
data.openObject();
Result res = um->accessUser(username, [&](auth::User const& user) {
DatabaseFeature::DATABASE->enumerateDatabases(
[&](TRI_vocbase_t& vocbase)->void {
if (full) {
auto lvl = user.configuredDBAuthLevel(vocbase.name());
std::string str = convertFromAuthLevel(lvl);
velocypack::ObjectBuilder b(&data, vocbase.name(), true);
data.add("permission", VPackValue(str));
velocypack::ObjectBuilder b2(&data, "collections", true);
methods::Collections::enumerate(
&vocbase,
[&](LogicalCollection& c)->void {
lvl =
user.configuredCollectionAuthLevel(vocbase.name(), c.name());
data.add(
c.name(),
velocypack::Value(convertFromAuthLevel(lvl))
);
}
);
lvl = user.configuredCollectionAuthLevel(vocbase.name(), "*");
data.add("*", velocypack::Value(convertFromAuthLevel(lvl)));
} else { // hide db's without access
auto lvl = user.databaseAuthLevel(vocbase.name());
if (lvl >= auth::Level::RO) {
data.add(vocbase.name(), VPackValue(convertFromAuthLevel(lvl)));
}
}
}
);
if (full) {
auth::Level lvl = user.databaseAuthLevel("*");
data("*", VPackValue(VPackValueType::Object))(
"permission", VPackValue(convertFromAuthLevel(lvl)))();
}
return TRI_ERROR_NO_ERROR;
});
data.close();
if (res.ok()) {
generateOk(ResponseCode::OK, data.slice());
} else {
generateError(res);
}
}
/// helper to create(0), replace(1), update(2) a user
static Result StoreUser(auth::UserManager* um, int mode, std::string const& user,
VPackSlice json) {
std::string passwd;
bool active = true;
VPackSlice extra;
if (json.isObject()) {
VPackSlice s = json.get("passwd");
passwd = s.isString() ? s.copyString() : "";
s = json.get("active");
active = s.isBool() ? s.getBool() : true;
extra = json.get("extra");
}
Result r;
if (mode == 0 || mode == 1) {
r = um->storeUser(mode == 1, user, passwd, active, extra);
} else if (mode == 2) {
r = um->updateUser(user, [&](auth::User& entry) {
if (json.isObject()) {
if (json.get("passwd").isString()) {
entry.updatePassword(passwd);
}
if (json.get("active").isBool()) {
entry.setActive(active);
}
}
if (extra.isObject() && !extra.isEmptyObject()) {
entry.setUserData(VPackBuilder(extra));
}
return TRI_ERROR_NO_ERROR;
});
}
return r;
}
RestStatus RestUsersHandler::postRequest(auth::UserManager* um) {
std::vector<std::string> suffixes = _request->decodedSuffixes();
bool parseSuccess = false;
VPackSlice const body = this->parseVPackBody(parseSuccess);
if (!parseSuccess || !body.isObject()) {
generateResult(rest::ResponseCode::OK, VPackSlice());
return RestStatus::DONE;
}
if (suffixes.empty()) {
// create a new user
if (isAdminUser()) {
VPackSlice s = body.get("user");
std::string user = s.isString() ? s.copyString() : "";
// create user
Result r = StoreUser(um, 0, user, body);
if (r.ok()) {
VPackBuilder doc = um->serializeUser(user);
generateUserResult(ResponseCode::CREATED, doc);
} else {
generateError(r);
}
} else {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
}
} else if (suffixes.size() == 1) {
// validate username / password
std::string const& user = suffixes[0];
std::string password;
VPackSlice s = body.get("passwd");
if (s.isString()) {
password = s.copyString();
}
if (um->checkPassword(user, password)) {
generateOk(rest::ResponseCode::OK, VPackSlice::trueSlice());
} else {
generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_USER_NOT_FOUND);
}
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
return RestStatus::DONE;
}
RestStatus RestUsersHandler::putRequest(auth::UserManager* um) {
std::vector<std::string> suffixes = _request->decodedSuffixes();
bool parseSuccess = false;
VPackSlice const body = this->parseVPackBody(parseSuccess);
if (!parseSuccess) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
return RestStatus::DONE;
}
if (suffixes.size() == 1) {
// replace existing user
std::string const& user = suffixes[0];
if (!canAccessUser(user)) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
return RestStatus::DONE;
}
Result r = StoreUser(um, 1, user, body);
if (r.ok()) {
VPackBuilder doc = um->serializeUser(user);
generateUserResult(ResponseCode::OK, doc);
} else {
generateError(r);
}
} else if (suffixes.size() == 3 || suffixes.size() == 4) {
// update database / collection permissions
std::string const& name = suffixes[0];
if (suffixes[1] == "database") {
// update a user's permissions
std::string const& db = suffixes[2];
std::string coll = suffixes.size() == 4 ? suffixes[3] : "";
if (!isAdminUser()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
return RestStatus::DONE;
}
// validate that the collection is present
if (suffixes.size() > 3) {
auto res = existsCollection(db, coll);
if (!res.ok()) {
generateError(res);
return RestStatus::DONE;
}
}
if (!body.isObject() ||
!body.get("grant").isString()) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
return RestStatus::DONE;
}
VPackSlice grant = body.get("grant");
auth::Level lvl = auth::convertToAuthLevel(grant);
// contains response in case of success
VPackBuilder b;
b(VPackValue(VPackValueType::Object));
Result r = um->updateUser(name, [&](auth::User& entry) {
if (coll.empty()) {
entry.grantDatabase(db, lvl);
b(db, VPackValue(convertFromAuthLevel(lvl)))();
} else {
entry.grantCollection(db, coll, lvl);
b(db + "/" + coll, VPackValue(convertFromAuthLevel(lvl)))();
}
return TRI_ERROR_NO_ERROR;
});
if (r.ok()) {
generateUserResult(ResponseCode::OK, b);
} else {
generateError(r);
}
} else if (suffixes[1] == "config") {
// update internal config data, used in the admin dashboard
if (!canAccessUser(name)) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
return RestStatus::DONE;
}
Result res;
if (!body.isNone()) {
std::string const& key = suffixes[2];
// The API expects: { value : <toStore> }
// If we get sth else than the above it is translated
// to a remove of the config option.
res = um->updateUser(name, [&](auth::User& u) {
VPackSlice newVal = body;
VPackSlice oldConf = u.userData();
if (!newVal.isObject() || !newVal.hasKey("value")) {
if (oldConf.isObject() && oldConf.hasKey(key)) {
u.setUserData(VPackCollection::remove(oldConf,
std::unordered_set<std::string>{key}));
} // Nothing to do. We do not have a config yet.
} else { // We need to merge the new key into the config
VPackBuilder b;
b(VPackValue(VPackValueType::Object))(key, newVal.get("value"))();
if (oldConf.isObject() && !oldConf.isEmptyObject()) { // merge value in
u.setUserData(VPackCollection::merge(oldConf, b.slice(), false));
} else {
u.setUserData(std::move(b));
}
}
return TRI_ERROR_NO_ERROR;
});
}
if (res.ok()) {
resetResponse(ResponseCode::OK);
} else {
generateError(res);
}
}
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
return RestStatus::DONE;
}
RestStatus RestUsersHandler::patchRequest(auth::UserManager* um) {
std::vector<std::string> const& suffixes = _request->decodedSuffixes();
bool parseSuccess = false;
VPackSlice const body = this->parseVPackBody(parseSuccess);
if (!parseSuccess) {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
return RestStatus::DONE;
}
if (suffixes.size() == 1) {
std::string const& user = suffixes[0];
if (canAccessUser(user)) {
// update a user
Result r = StoreUser(um, 2, user, body);
if (r.ok()) {
VPackBuilder doc = um->serializeUser(user);
generateUserResult(ResponseCode::OK, doc);
} else {
generateError(r);
}
} else {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
}
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
return RestStatus::DONE;
}
RestStatus RestUsersHandler::deleteRequest(auth::UserManager* um) {
std::vector<std::string> suffixes = _request->decodedSuffixes();
if (suffixes.size() == 1) {
if (!isAdminUser()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
return RestStatus::DONE;
}
std::string const& user = suffixes[0];
Result r = um->removeUser(user);
if (r.ok()) {
VPackBuilder b;
b(VPackValue(VPackValueType::Object))(StaticStrings::Error, VPackValue(false))(
StaticStrings::Code, VPackValue(202))();
generateResult(ResponseCode::ACCEPTED, b.slice());
} else {
generateError(r);
}
} else if (suffixes.size() == 2) {
std::string const& user = suffixes[0];
if (suffixes[1] == "config" && canAccessUser(user)) {
Result r = um->updateUser(user, [&](auth::User& u) {
u.setConfigData(VPackBuilder());
return TRI_ERROR_NO_ERROR;
});
if (r.ok()) {
resetResponse(ResponseCode::OK);
} else {
generateError(r);
}
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
} else if (suffixes.size() == 3 || suffixes.size() == 4) {
std::string const& user = suffixes[0];
if (suffixes[1] == "database") {
// revoke a user's permissions
std::string const& db = suffixes[2];
std::string coll = suffixes.size() == 4 ? suffixes[3] : "";
if (!isAdminUser()) {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
return RestStatus::DONE;
}
// validate that the collection is present
if (suffixes.size() > 3) {
auto res = existsCollection(db, coll);
if (!res.ok()) {
generateError(res);
return RestStatus::DONE;
}
}
Result r = um->updateUser(user, [&](auth::User& entry) {
if (coll.empty()) {
entry.removeDatabase(db);
} else {
entry.removeCollection(db, coll);
}
return TRI_ERROR_NO_ERROR;
});
if (r.ok()) {
VPackBuilder b;
b(VPackValue(VPackValueType::Object))(StaticStrings::Error, VPackValue(false))(
StaticStrings::Code, VPackValue(202))();
generateResult(ResponseCode::ACCEPTED, b.slice());
} else {
generateError(r);
}
} else if (suffixes[1] == "config") {
// remove internal config data, used in the WebUI
if (canAccessUser(user)) {
std::string const& key = suffixes[2];
Result r = um->updateUser(user, [&](auth::User& u) {
VPackBuilder b;
b(VPackValue(VPackValueType::Object))(key, VPackSlice::nullSlice())();
if (!u.configData().isNone()) {
u.setConfigData(VPackCollection::merge(u.configData(), b.slice(), false, true));
}
return TRI_ERROR_NO_ERROR;
});
if (r.ok()) {
resetResponse(ResponseCode::OK);
} else {
generateError(r);
}
} else {
generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN);
}
}
} else {
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
}
return RestStatus::DONE;
}