1
0
Fork 0

Merge branch 'devel' of ssh://github.com/ArangoDB/ArangoDB into devel

This commit is contained in:
Max Neunhoeffer 2016-10-17 15:14:13 +02:00
commit d133b920ba
58 changed files with 1171 additions and 524 deletions

84
README
View File

@ -1,84 +0,0 @@
****** ArangoDB ******
ArangoDB is a multi-model, open-source database with flexible data models for
documents, graphs, and key-values. Build high performance applications using a
convenient SQL-like query language or JavaScript extensions. Use ACID
transactions if you require them. Scale horizontally with a few mouse clicks.
The supported data models can be mixed in queries and allow ArangoDB to be the
aggregation point for your data.
To get started, try one of our 10 minutes tutorials in your favorite
programming language or try one of our ArangoDB_Cookbook_recipes.
For the impatient: download and install ArangoDB. Start the server arangod and
point your browser to http://127.0.0.1:8529/.
***** Key Features in ArangoDB *****
* Multi-Model: Documents, graphs and key-value pairs — model your data as
you see fit for your application.
* Joins: Conveniently join what belongs together for flexible ad-hoc
querying, less data redundancy.
* Transactions: Easy application development keeping your data consistent
and safe. No hassle in your client.
Here is an AQL query that makes use of all those features:
[AQL Query Example]
Joins and transactions are key features for flexible, secure data designs,
widely used in relational databases but lacking in many NoSQL products.
However, there is no need to forgo them in ArangoDB. You decide how and when to
use joins and strong consistency guarantees, without sacrificing performance
and scalability.
Furthermore, ArangoDB offers a JavaScript framework called Foxx that is
executed in the database server with direct access to the data. Build your own
data-centric microservices with a few lines of code:
Microservice Example
[Microservice Example]
By extending the HTTP API with user code written in JavaScript, ArangoDB can be
turned into a strict schema-enforcing persistence engine.
Next step, bundle your Foxx application as a docker_container and get it
running in the cloud.
Other features of ArangoDB include:
* Schema-free schemata let you combine the space efficiency of MySQL with
the performance power of NoSQL
* Use a data-centric microservices approach with ArangoDB Foxx and fuse
your application-logic and database together for maximal throughput
* JavaScript for all: no language zoo, you can use one language from your
browser to your back-end
* ArangoDB is multi-threaded - exploit the power of all your cores
* Flexible data modeling: model your data as combination of key-value
pairs, documents or graphs - perfect for social relations
* Free index choice: use the correct index for your problem, be it a skip
list or a fulltext search
* Configurable durability: let the application decide if it needs more
durability or more performance
* Powerful query language (AQL) to retrieve and modify data
* Transactions: run queries on multiple documents or collections with
optional transactional consistency and isolation
* Replication and Sharding: set up the database in a master-slave
configuration or spread bigger datasets across multiple servers
* It is open source (Apache License 2.0)
For more in-depth information read the design_goals_of_ArangoDB
***** Latest Release - ArangoDB 3.0 *****
The What's_new_in_ArangoDB_3.0 can be found in the documentation.
Key features of the 3.0 release are:
* use of VelocyPack as internal storage format
* AQL improvements
* much better cluster state management
* Synchronous replication (master/master)
* unified APIs for CRUD operations
* persistent indexes
* upgraded version of V8
* new web admin interface
* Foxx improvements
* Logging improvements
* improved documentation
***** More Information *****
Please check the Installation_Manual for installation and compilation
instructions.
The User_Manual has an introductory chapter showing the basic operations of
ArangoDB.
***** Stay in Contact *****
We really appreciate feature requests and bug reports. Please use our Github
issue tracker for reporting them:
https://github.com/arangodb/arangodb/issues
You can use the Google group for improvements, feature requests, comments:
http://www.arangodb.com/community
StackOverflow is great for questions about AQL, usage scenarios etc.
http://stackoverflow.com/questions/tagged/arangodb
To chat with the community and the developers we offer a Slack chat:
http://slack.arangodb.com/

View File

@ -94,17 +94,17 @@ The [What's new in ArangoDB 3.0](https://docs.arangodb.com/3.0/Manual/ReleaseNot
Key features of the 3.0 release are:
- use of VelocyPack as internal storage format
- Use of VelocyPack as internal storage format
- AQL improvements
- much better cluster state management
- Much better cluster state management
- Synchronous replication (master/master)
- unified APIs for CRUD operations
- persistent indexes
- upgraded version of V8
- new web admin interface
- Unified APIs for CRUD operations
- Persistent indexes
- Upgraded version of V8
- New web admin interface
- Foxx improvements
- Logging improvements
- improved documentation
- Improved documentation
More Information
----------------

View File

@ -202,6 +202,7 @@ add_executable(${BIN_ARANGOD}
FulltextIndex/fulltext-query.cpp
FulltextIndex/fulltext-result.cpp
GeneralServer/AsyncJobManager.cpp
GeneralServer/AuthenticationFeature.cpp
GeneralServer/GeneralCommTask.cpp
GeneralServer/GeneralListenTask.cpp
GeneralServer/GeneralServer.cpp

View File

@ -32,9 +32,10 @@
#include "Basics/StringUtils.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/WriteLocker.h"
#include "Cluster/ClusterComm.h"
#include "Cluster/ServerState.h"
#include "Endpoint/Endpoint.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Logger/Logger.h"
#include "Random/RandomGenerator.h"
#include "Rest/HttpRequest.h"
@ -560,7 +561,7 @@ bool AgencyComm::tryInitializeStructure(std::string const& jwtSecret) {
addEmptyVPackObject("DBServers", builder);
}
builder.add("InitDone", VPackValue(true));
builder.add("Secret", VPackValue(encodeHex(jwtSecret)));
builder.add("Secret", VPackValue(jwtSecret));
} catch (std::exception const& e) {
LOG_TOPIC(ERR, Logger::STARTUP) << "Couldn't create initializing structure "
<< e.what();
@ -621,16 +622,22 @@ bool AgencyComm::shouldInitializeStructure() {
bool AgencyComm::ensureStructureInitialized() {
LOG_TOPIC(TRACE, Logger::STARTUP) << "Checking if agency is initialized";
GeneralServerFeature* restServer =
application_features::ApplicationServer::getFeature<GeneralServerFeature>(
"GeneralServer");
AuthenticationFeature* authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(authentication != nullptr);
while (true) {
while (shouldInitializeStructure()) {
LOG_TOPIC(TRACE, Logger::STARTUP)
<< "Agency is fresh. Needs initial structure.";
// mop: we initialized it .. great success
if (tryInitializeStructure(restServer->jwtSecret())) {
std::string secret;
if (authentication->isEnabled()) {
secret = authentication->jwtSecret();
}
if (tryInitializeStructure(secret)) {
LOG_TOPIC(TRACE, Logger::STARTUP) << "Successfully initialized agency";
break;
}
@ -667,8 +674,10 @@ bool AgencyComm::ensureStructureInitialized() {
LOG(ERR) << "Couldn't find secret in agency!";
return false;
}
restServer->setJwtSecret(decodeHex(secretValue.copyString()));
std::string const secret = secretValue.copyString();
if (!secret.empty()) {
authentication->setJwtSecret(secretValue.copyString());
}
return true;
}
@ -1768,7 +1777,7 @@ AgencyCommResult AgencyComm::send(
<< "': " << body;
arangodb::httpclient::SimpleHttpClient client(connection, timeout, false);
client.setJwt(ClusterComm::instance()->jwt());
client.keepConnectionOnDestruction(true);
// set up headers

View File

@ -30,6 +30,7 @@
#include "Basics/StringUtils.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ServerState.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Logger/Logger.h"
#include "Scheduler/JobGuard.h"
#include "Scheduler/SchedulerFeature.h"
@ -205,8 +206,25 @@ char const* ClusterCommResult::stringifyStatus(ClusterCommOpStatus status) {
////////////////////////////////////////////////////////////////////////////////
ClusterComm::ClusterComm()
: _backgroundThread(nullptr), _logConnectionErrors(false) {
_communicator = std::make_shared<communicator::Communicator>();
: _backgroundThread(nullptr),
_logConnectionErrors(false),
_authenticationEnabled(false),
_jwt(""),
_jwtAuthorization("") {
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>("Authentication");
TRI_ASSERT(authentication != nullptr);
if (authentication->isEnabled()) {
_authenticationEnabled = true;
VPackBuilder bodyBuilder;
{
VPackObjectBuilder p(&bodyBuilder);
bodyBuilder.add("server_id", VPackValue(ServerState::instance()->getId()));
}
_jwt = authentication->authInfo()->generateJwt(bodyBuilder);
_jwtAuthorization = "bearer " + _jwt;
}
_communicator = std::make_shared<communicator::Communicator>();
}
////////////////////////////////////////////////////////////////////////////////
@ -737,7 +755,8 @@ void ClusterComm::asyncAnswer(std::string& coordinatorHeader,
headers["X-Arango-Coordinator"] = coordinatorHeader;
headers["X-Arango-Response-Code"] =
responseToSend->responseString(responseToSend->responseCode());
headers["Authorization"] = ServerState::instance()->getAuthentication();
addAuthorization(&headers);
TRI_voc_tick_t timeStamp = TRI_HybridLogicalClock();
headers[StaticStrings::HLCHeader] =
arangodb::basics::HybridLogicalClock::encodeTimeStamp(timeStamp);
@ -1222,7 +1241,7 @@ std::pair<ClusterCommResult*, HttpRequest*> ClusterComm::prepareRequest(std::str
}
}
}
headersCopy["Authorization"] = ServerState::instance()->getAuthentication();
addAuthorization(&headersCopy);
TRI_voc_tick_t timeStamp = TRI_HybridLogicalClock();
headersCopy[StaticStrings::HLCHeader] =
arangodb::basics::HybridLogicalClock::encodeTimeStamp(timeStamp);
@ -1248,6 +1267,12 @@ std::pair<ClusterCommResult*, HttpRequest*> ClusterComm::prepareRequest(std::str
return std::make_pair(result, request);
}
void ClusterComm::addAuthorization(std::unordered_map<std::string, std::string>* headers) {
if (_authenticationEnabled) {
headers->emplace("Authorization", _jwtAuthorization);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief ClusterComm main loop
////////////////////////////////////////////////////////////////////////////////

View File

@ -524,17 +524,14 @@ class ClusterComm {
ClusterCommTimeout timeout, size_t& nrDone,
arangodb::LogTopic const& logTopic);
//////////////////////////////////////////////////////////////////////////////
/// @brief this is the fast path method for performRequests for the case
/// of only a single request in the vector. In this case we can use a single
/// syncRequest, which saves a network roundtrip. This is an important
/// optimization for the single document operation case.
/// Exact same semantics as performRequests.
//////////////////////////////////////////////////////////////////////////////
std::shared_ptr<communicator::Communicator> communicator() {
return _communicator;
}
void addAuthorization(std::unordered_map<std::string, std::string>* headers);
std::string jwt() { return _jwt; };
private:
size_t performSingleRequest(std::vector<ClusterCommRequest>& requests,
ClusterCommTimeout timeout, size_t& nrDone,
@ -635,6 +632,9 @@ class ClusterComm {
bool _logConnectionErrors;
std::shared_ptr<communicator::Communicator> _communicator;
bool _authenticationEnabled;
std::string _jwt;
std::string _jwtAuthorization;
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -57,6 +57,7 @@ ClusterFeature::ClusterFeature(application_features::ApplicationServer* server)
_agencyCallbackRegistry(nullptr) {
setOptional(true);
requiresElevatedPrivileges(false);
startsAfter("Authentication");
startsAfter("Logger");
startsAfter("WorkMonitor");
startsAfter("Database");
@ -191,7 +192,6 @@ void ClusterFeature::validateOptions(std::shared_ptr<ProgramOptions> options) {
}
void ClusterFeature::prepare() {
ServerState::instance()->setAuthentication(_username, _password);
ServerState::instance()->setDataPath(_dataPath);
ServerState::instance()->setLogPath(_logPath);
ServerState::instance()->setArangodPath(_arangodPath);

View File

@ -36,7 +36,7 @@
#include "Cluster/ClusterMethods.h"
#include "Cluster/DBServerAgencySync.h"
#include "Cluster/ServerState.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "GeneralServer/RestHandlerFactory.h"
#include "Logger/Logger.h"
#include "RestServer/DatabaseFeature.h"
@ -306,6 +306,10 @@ void HeartbeatThread::runDBServer() {
////////////////////////////////////////////////////////////////////////////////
void HeartbeatThread::runCoordinator() {
AuthenticationFeature* authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(authentication != nullptr);
LOG_TOPIC(TRACE, Logger::HEARTBEAT)
<< "starting heartbeat thread (coordinator version)";
@ -424,7 +428,9 @@ void HeartbeatThread::runCoordinator() {
if (userVersion > 0 && userVersion != oldUserVersion) {
oldUserVersion = userVersion;
GeneralServerFeature::AUTH_INFO.outdate();
if (authentication->isEnabled()) {
authentication->authInfo()->outdate();
}
}
}

View File

@ -50,7 +50,6 @@ ServerState::ServerState()
_dbserverConfig(),
_coordinatorConfig(),
_address(),
_authentication(),
_lock(),
_role(),
_idOfPrimary(""),
@ -160,22 +159,6 @@ std::string ServerState::stateToString(StateEnum state) {
return "";
}
////////////////////////////////////////////////////////////////////////////////
/// @brief set the authentication data for cluster-internal communication
////////////////////////////////////////////////////////////////////////////////
void ServerState::setAuthentication(std::string const& username,
std::string const& password) {
_authentication =
"Basic " + basics::StringUtils::encodeBase64(username + ":" + password);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the authentication data for cluster-internal communication
////////////////////////////////////////////////////////////////////////////////
std::string ServerState::getAuthentication() { return _authentication; }
////////////////////////////////////////////////////////////////////////////////
/// @brief find and set our role
////////////////////////////////////////////////////////////////////////////////

View File

@ -88,12 +88,6 @@ class ServerState {
/// @brief sets the initialized flag
void setClusterEnabled() { _clusterEnabled = true; }
/// @brief set the authentication data for cluster-internal communication
void setAuthentication(std::string const&, std::string const&);
/// @brief get the authentication data for cluster-internal communication
std::string getAuthentication();
/// @brief flush the server state (used for testing)
void flush();
@ -312,9 +306,6 @@ class ServerState {
/// @brief the server's own address, can be set just once
std::string _address;
/// @brief the authentication data used for cluster-internal communication
std::string _authentication;
/// @brief r/w lock for state
arangodb::basics::ReadWriteLock _lock;

View File

@ -26,6 +26,7 @@
#include "Cluster/ClusterInfo.h"
#include "Cluster/ServerState.h"
#include "Cluster/ClusterComm.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "V8/v8-buffer.h"
#include "V8/v8-conv.h"
#include "V8/v8-globals.h"
@ -2015,6 +2016,34 @@ static void JS_GetId(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_END
}
static void JS_ClusterDownload(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
AuthenticationFeature* authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>("Authentication");
if (authentication->isEnabled()) {
// mop: really quick and dirty
v8::Handle<v8::Object> options = v8::Object::New(isolate);
v8::Handle<v8::Object> headers = v8::Object::New(isolate);
if (args.Length() > 2) {
if (args[2]->IsObject()) {
options = v8::Handle<v8::Object>::Cast(args[2]);
if (options->Has(TRI_V8_ASCII_STRING("headers"))) {
headers = v8::Handle<v8::Object>::Cast(options->Get(TRI_V8_ASCII_STRING("headers")));
}
}
}
options->Set(TRI_V8_ASCII_STRING("headers"), headers);
std::string const authorization = "bearer " + ClusterComm::instance()->jwt();
v8::Handle<v8::String> v8Authorization = TRI_V8_STD_STRING(authorization);
headers->Set(TRI_V8_ASCII_STRING("Authorization"), v8Authorization);
args[2] = options;
}
TRI_V8_TRY_CATCH_END
return JS_Download(args);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief creates a global cluster context
////////////////////////////////////////////////////////////////////////////////
@ -2227,4 +2256,7 @@ void TRI_InitV8Cluster(v8::Isolate* isolate, v8::Handle<v8::Context> context) {
TRI_AddGlobalVariableVocbase(isolate, context,
TRI_V8_ASCII_STRING("ArangoClusterComm"), ss);
}
TRI_AddGlobalFunctionVocbase(
isolate, context, TRI_V8_ASCII_STRING("SYS_CLUSTER_DOWNLOAD"),
JS_ClusterDownload);
}

View File

@ -0,0 +1,150 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 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 <andreas@arangodb.com>
////////////////////////////////////////////////////////////////////////////////
#include "AuthenticationFeature.h"
#include "ProgramOptions/ProgramOptions.h"
#include "RestServer/QueryRegistryFeature.h"
#include "Random/RandomGenerator.h"
using namespace arangodb;
using namespace arangodb::options;
AuthenticationFeature::AuthenticationFeature(
application_features::ApplicationServer* server)
: ApplicationFeature(server, "Authentication"),
_authenticationUnixSockets(true),
_authenticationSystemOnly(true),
_jwtSecretProgramOption(""),
_active(true) {
setOptional(true);
requiresElevatedPrivileges(false);
startsAfter("Random");
}
void AuthenticationFeature::collectOptions(
std::shared_ptr<ProgramOptions> options) {
options->addSection("server", "Server features");
options->addOldOption("server.disable-authentication",
"server.authentication");
options->addOldOption("server.disable-authentication-unix-sockets",
"server.authentication-unix-sockets");
options->addOldOption("server.authenticate-system-only",
"server.authentication-system-only");
options->addOldOption("server.allow-method-override",
"http.allow-method-override");
options->addOldOption("server.hide-product-header",
"http.hide-product-header");
options->addOldOption("server.keep-alive-timeout", "http.keep-alive-timeout");
options->addOldOption("server.default-api-compatibility", "");
options->addOldOption("no-server", "server.rest-server");
options->addOption("--server.authentication",
"enable or disable authentication for ALL client requests",
new BooleanParameter(&_active));
options->addOption(
"--server.authentication-system-only",
"use HTTP authentication only for requests to /_api and /_admin",
new BooleanParameter(&_authenticationSystemOnly));
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
options->addOption("--server.authentication-unix-sockets",
"authentication for requests via UNIX domain sockets",
new BooleanParameter(&_authenticationUnixSockets));
#endif
options->addOption("--server.jwt-secret",
"secret to use when doing jwt authentication",
new StringParameter(&_jwtSecretProgramOption));
}
void AuthenticationFeature::validateOptions(std::shared_ptr<ProgramOptions>) {
if (!_active) {
forceDisable();
return;
}
if (!_jwtSecretProgramOption.empty()) {
if (_jwtSecretProgramOption.length() > _maxSecretLength) {
LOG(ERR) << "Given JWT secret too long. Max length is "
<< _maxSecretLength;
FATAL_ERROR_EXIT();
}
}
}
std::string AuthenticationFeature::generateNewJwtSecret() {
std::string jwtSecret = "";
uint16_t m = 254;
for (size_t i = 0; i < _maxSecretLength; i++) {
jwtSecret += (1 + RandomGenerator::interval(m));
}
return jwtSecret;
}
void AuthenticationFeature::start() {
LOG(INFO) << "Authentication is turned " << (_active ? "on" : "off");
if (!isEnabled()) {
return;
}
auto queryRegistryFeature =
application_features::ApplicationServer::getFeature<QueryRegistryFeature>("QueryRegistry");
authInfo()->setQueryRegistry(queryRegistryFeature->queryRegistry());
if (_authenticationSystemOnly) {
LOG(INFO) << "Authentication system only";
}
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
LOG(INFO) << "Authentication for unix sockets is turned "
<< (_authenticationUnixSockets ? "on" : "off");
#endif
}
AuthInfo* AuthenticationFeature::authInfo() {
// mop: catch misused stuff..authentication is disabled...why would you
// need any authentication info?
TRI_ASSERT(isEnabled());
return &_authInfo;
}
void AuthenticationFeature::unprepare() {
}
void AuthenticationFeature::prepare() {
if (!isEnabled()) {
return;
}
std::string jwtSecret = _jwtSecretProgramOption;
if (jwtSecret.empty()) {
jwtSecret = generateNewJwtSecret();
}
authInfo()->setJwtSecret(jwtSecret);
}
void AuthenticationFeature::stop() {
}

View File

@ -0,0 +1,64 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 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 <andreas@arangodb.com>
////////////////////////////////////////////////////////////////////////////////
#ifndef APPLICATION_FEATURES_AUTHENTICATION_FEATURE_H
#define APPLICATION_FEATURES_AUTHENTICATION_FEATURE_H 1
#include "ApplicationFeatures/ApplicationFeature.h"
#include "VocBase/AuthInfo.h"
namespace arangodb {
class AuthenticationFeature final
: public application_features::ApplicationFeature {
private:
const size_t _maxSecretLength = 64;
public:
explicit AuthenticationFeature(application_features::ApplicationServer*);
public:
void collectOptions(std::shared_ptr<options::ProgramOptions>) override final;
void validateOptions(std::shared_ptr<options::ProgramOptions>) override final;
void prepare() override final;
void start() override final;
void stop() override final;
void unprepare() override final;
private:
AuthInfo _authInfo;
bool _authenticationUnixSockets;
bool _authenticationSystemOnly;
std::string _jwtSecretProgramOption;
bool _active;
public:
bool authenticationUnixSockets() const { return _authenticationUnixSockets; }
bool authenticationSystemOnly() const { return _authenticationSystemOnly; }
std::string jwtSecret() { return authInfo()->jwtSecret(); }
std::string generateNewJwtSecret();
void setJwtSecret(std::string const& jwtSecret) { authInfo()->setJwtSecret(jwtSecret); }
AuthInfo* authInfo();
};
};
#endif

View File

@ -33,13 +33,13 @@
#include "Cluster/RestAgencyCallbacksHandler.h"
#include "Cluster/RestShardHandler.h"
#include "Cluster/TraverserEngineRegistry.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "GeneralServer/GeneralServer.h"
#include "GeneralServer/RestHandlerFactory.h"
#include "InternalRestHandler/InternalRestTraverserHandler.h"
#include "ProgramOptions/Parameters.h"
#include "ProgramOptions/ProgramOptions.h"
#include "ProgramOptions/Section.h"
#include "Random/RandomGenerator.h"
#include "Rest/Version.h"
#include "RestHandler/RestAdminLogHandler.h"
#include "RestHandler/RestAqlFunctionsHandler.h"
@ -85,17 +85,12 @@ using namespace arangodb::options;
rest::RestHandlerFactory* GeneralServerFeature::HANDLER_FACTORY = nullptr;
rest::AsyncJobManager* GeneralServerFeature::JOB_MANAGER = nullptr;
GeneralServerFeature* GeneralServerFeature::GENERAL_SERVER = nullptr;
AuthInfo GeneralServerFeature::AUTH_INFO;
GeneralServerFeature::GeneralServerFeature(
application_features::ApplicationServer* server)
: ApplicationFeature(server, "GeneralServer"),
_allowMethodOverride(false),
_authentication(true),
_authenticationUnixSockets(true),
_authenticationSystemOnly(true),
_proxyCheck(true),
_jwtSecret(""),
_verificationMode(SSL_VERIFY_NONE),
_verificationCallback(nullptr),
_handlerFactory(nullptr),
@ -103,6 +98,7 @@ GeneralServerFeature::GeneralServerFeature(
setOptional(true);
requiresElevatedPrivileges(false);
startsAfter("Agency");
startsAfter("Authentication");
startsAfter("CheckVersion");
startsAfter("Database");
startsAfter("Endpoint");
@ -118,12 +114,6 @@ void GeneralServerFeature::collectOptions(
std::shared_ptr<ProgramOptions> options) {
options->addSection("server", "Server features");
options->addOldOption("server.disable-authentication",
"server.authentication");
options->addOldOption("server.disable-authentication-unix-sockets",
"server.authentication-unix-sockets");
options->addOldOption("server.authenticate-system-only",
"server.authentication-system-only");
options->addOldOption("server.allow-method-override",
"http.allow-method-override");
options->addOldOption("server.hide-product-header",
@ -132,25 +122,6 @@ void GeneralServerFeature::collectOptions(
options->addOldOption("server.default-api-compatibility", "");
options->addOldOption("no-server", "server.rest-server");
options->addOption("--server.authentication",
"enable or disable authentication for ALL client requests",
new BooleanParameter(&_authentication));
options->addOption(
"--server.authentication-system-only",
"use HTTP authentication only for requests to /_api and /_admin",
new BooleanParameter(&_authenticationSystemOnly));
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
options->addOption("--server.authentication-unix-sockets",
"authentication for requests via UNIX domain sockets",
new BooleanParameter(&_authenticationUnixSockets));
#endif
options->addOption("--server.jwt-secret",
"secret to use when doing jwt authentication",
new StringParameter(&_jwtSecret));
options->addSection("http", "HttpServer features");
options->addHiddenOption("--http.allow-method-override",
@ -211,14 +182,6 @@ void GeneralServerFeature::validateOptions(std::shared_ptr<ProgramOptions>) {
}),
_accessControlAllowOrigins.end());
}
if (!_jwtSecret.empty()) {
if (_jwtSecret.length() > GeneralServerFeature::_maxSecretLength) {
LOG(ERR) << "Given JWT secret too long. Max length is "
<< GeneralServerFeature::_maxSecretLength;
FATAL_ERROR_EXIT();
}
}
}
static TRI_vocbase_t* LookupDatabaseFromRequest(GeneralRequest* request) {
@ -247,6 +210,8 @@ static TRI_vocbase_t* LookupDatabaseFromRequest(GeneralRequest* request) {
}
static bool SetRequestContext(GeneralRequest* request, void* data) {
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>("Authentication");
TRI_ASSERT(authentication != nullptr);
TRI_vocbase_t* vocbase = LookupDatabaseFromRequest(request);
// invalid database name specified, database not found etc.
@ -261,27 +226,14 @@ static bool SetRequestContext(GeneralRequest* request, void* data) {
}
VocbaseContext* ctx = new arangodb::VocbaseContext(
request, vocbase, GeneralServerFeature::getJwtSecret());
request, vocbase);
request->setRequestContext(ctx, true);
// the "true" means the request is the owner of the context
return true;
}
void GeneralServerFeature::generateNewJwtSecret() {
_jwtSecret = "";
uint16_t m = 254;
for (size_t i = 0; i < GeneralServerFeature::_maxSecretLength; i++) {
_jwtSecret += (1 + RandomGenerator::interval(m));
}
}
void GeneralServerFeature::prepare() {
if (_jwtSecret.empty()) {
generateNewJwtSecret();
}
RestHandlerFactory::setMaintenance(true);
GENERAL_SERVER = this;
}
@ -302,22 +254,13 @@ void GeneralServerFeature::start() {
server->startListening();
}
LOG(INFO) << "Authentication is turned " << (_authentication ? "on" : "off");
if (_authentication) {
if (_authenticationSystemOnly) {
LOG(INFO) << "Authentication system only";
}
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
LOG(INFO) << "Authentication for unix sockets is turned "
<< (_authenticationUnixSockets ? "on" : "off");
#endif
}
// populate the authentication cache. otherwise no one can access the new
// database
GeneralServerFeature::AUTH_INFO.outdate();
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>("Authentication");
TRI_ASSERT(authentication != nullptr);
if (authentication->isEnabled()) {
authentication->authInfo()->outdate();
}
}
void GeneralServerFeature::stop() {
@ -382,6 +325,11 @@ void GeneralServerFeature::defineHandlers() {
application_features::ApplicationServer::getFeature<ClusterFeature>(
"Cluster");
TRI_ASSERT(cluster != nullptr);
AuthenticationFeature* authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(authentication != nullptr);
auto queryRegistry = QueryRegistryFeature::QUERY_REGISTRY;
auto traverserEngineRegistry =
@ -551,11 +499,12 @@ void GeneralServerFeature::defineHandlers() {
_handlerFactory->addPrefixHandler(
"/_admin/shutdown",
RestHandlerCreator<arangodb::RestShutdownHandler>::createNoData);
_handlerFactory->addPrefixHandler(
"/_open/auth", RestHandlerCreator<arangodb::RestAuthHandler>::createData<
std::string const*>,
&_jwtSecret);
if (authentication->isEnabled()) {
_handlerFactory->addPrefixHandler(
"/_open/auth",
RestHandlerCreator<arangodb::RestAuthHandler>::createNoData);
}
// ...........................................................................
// /_admin

View File

@ -30,7 +30,6 @@
#include "Basics/asio-helper.h"
#include "Actions/RestActionHandler.h"
#include "VocBase/AuthInfo.h"
namespace arangodb {
namespace rest {
@ -51,7 +50,6 @@ class GeneralServerFeature final
public:
static rest::RestHandlerFactory* HANDLER_FACTORY;
static rest::AsyncJobManager* JOB_MANAGER;
static AuthInfo AUTH_INFO;
public:
static double keepAliveTimeout() {
@ -72,10 +70,7 @@ class GeneralServerFeature final
static verification_callback_asio verificationCallbackAsio() {
return GENERAL_SERVER->_verificationCallbackAsio;
};
static bool authenticationEnabled() {
return GENERAL_SERVER != nullptr && GENERAL_SERVER->_authentication;
}
static bool hasProxyCheck() {
return GENERAL_SERVER != nullptr && GENERAL_SERVER->proxyCheck();
}
@ -88,14 +83,6 @@ class GeneralServerFeature final
return GENERAL_SERVER->trustedProxies();
}
static std::string getJwtSecret() {
if (GENERAL_SERVER == nullptr) {
return std::string();
}
return GENERAL_SERVER->jwtSecret();
}
static bool allowMethodOverride() {
if (GENERAL_SERVER == nullptr) {
return false;
@ -116,7 +103,6 @@ class GeneralServerFeature final
private:
static GeneralServerFeature* GENERAL_SERVER;
static const size_t _maxSecretLength = 64;
public:
explicit GeneralServerFeature(application_features::ApplicationServer*);
@ -139,28 +125,18 @@ class GeneralServerFeature final
private:
double _keepAliveTimeout = 300.0;
bool _allowMethodOverride;
bool _authentication;
bool _authenticationUnixSockets;
bool _authenticationSystemOnly;
bool _proxyCheck;
std::vector<std::string> _trustedProxies;
std::vector<std::string> _accessControlAllowOrigins;
std::string _jwtSecret;
int _verificationMode;
verification_callback_fptr _verificationCallback;
verification_callback_asio _verificationCallbackAsio;
public:
bool authentication() const { return _authentication; }
bool authenticationUnixSockets() const { return _authenticationUnixSockets; }
bool authenticationSystemOnly() const { return _authenticationSystemOnly; }
bool proxyCheck() const { return _proxyCheck; }
std::vector<std::string> trustedProxies() const { return _trustedProxies; }
std::string jwtSecret() const { return _jwtSecret; }
void generateNewJwtSecret();
void setJwtSecret(std::string const& jwtSecret) { _jwtSecret = jwtSecret; }
private:
void buildServers();

View File

@ -152,6 +152,7 @@ void HttpCommTask::addResponse(HttpResponse* response) {
if (!buffer->empty()) {
LOG_TOPIC(TRACE, Logger::REQUESTS)
<< "\"http-request-response\",\"" << (void*)this << "\",\""
<< _fullUrl << "\",\""
<< StringUtils::escapeUnicode(
std::string(buffer->c_str(), buffer->length()))
<< "\"";

View File

@ -92,7 +92,7 @@ RestHandler* RestHandlerFactory::createHandler(
std::unique_ptr<GeneralRequest> request,
std::unique_ptr<GeneralResponse> response) const {
std::string const& path = request->requestPath();
// In the bootstrap phase, we would like that coordinators answer the
// following to endpoints, but not yet others:
if (_maintenanceMode.load()) {

View File

@ -35,6 +35,7 @@
#include "Basics/HybridLogicalClock.h"
#include "Basics/StringBuffer.h"
#include "Basics/VelocyPackHelper.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "GeneralServer/GeneralServer.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/RestHandler.h"
@ -62,10 +63,11 @@ VppCommTask::VppCommTask(EventLoop loop, GeneralServer* server,
GeneralCommTask(loop, server, std::move(socket), std::move(info),
timeout),
_authenticatedUser(),
_authenticationEnabled(
application_features::ApplicationServer::getFeature<
GeneralServerFeature>("GeneralServer")
->authenticationEnabled()) {
_authentication(nullptr) {
_authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(_authentication != nullptr);
_protocol = "vpp";
_readBuffer.reserve(
_bufferLength); // ATTENTION <- this is required so we do not
@ -180,6 +182,35 @@ bool VppCommTask::isChunkComplete(char* start) {
return true;
}
void VppCommTask::handleAuthentication(VPackSlice const& header, uint64_t messageId) {
// std::string encryption = header.at(2).copyString();
std::string user = header.at(3).copyString();
std::string pass = header.at(4).copyString();
bool authOk = false;
if (!_authentication->isEnabled()) {
authOk = true;
} else {
auto auth = basics::StringUtils::encodeBase64(user + ":" + pass);
AuthResult result = _authentication->authInfo()->checkAuthentication(
AuthInfo::AuthType::BASIC, auth);
authOk = result._authorized;
}
if (authOk) {
// mop: hmmm...user should be completely ignored if there is no auth IMHO
_authenticatedUser = std::move(user);
handleSimpleError(rest::ResponseCode::OK, TRI_ERROR_NO_ERROR,
"authentication successful", messageId);
} else {
_authenticatedUser.clear();
handleSimpleError(rest::ResponseCode::UNAUTHORIZED,
TRI_ERROR_HTTP_UNAUTHORIZED, "authentication failed",
messageId);
}
}
// reads data from the socket
bool VppCommTask::processRead() {
RequestStatisticsAgent agent(true);
@ -248,24 +279,7 @@ bool VppCommTask::processRead() {
// handle request types
if (type == 1000) {
// do authentication
// std::string encryption = header.at(2).copyString();
std::string user = header.at(3).copyString();
std::string pass = header.at(4).copyString();
auto auth = basics::StringUtils::encodeBase64(user + ":" + pass);
AuthResult result = GeneralServerFeature::AUTH_INFO.checkAuthentication(
AuthInfo::AuthType::BASIC, auth);
if (!_authenticationEnabled || result._authorized) {
_authenticatedUser = std::move(user);
handleSimpleError(rest::ResponseCode::OK, TRI_ERROR_NO_ERROR,
"authentication successful", chunkHeader._messageID);
} else {
_authenticatedUser.clear();
handleSimpleError(rest::ResponseCode::UNAUTHORIZED,
TRI_ERROR_HTTP_UNAUTHORIZED, "authentication failed",
chunkHeader._messageID);
}
handleAuthentication(header, chunkHeader._messageID);
} else {
// the handler will take ownersip of this pointer
std::unique_ptr<VppRequest> request(new VppRequest(
@ -276,9 +290,9 @@ bool VppCommTask::processRead() {
// check authentication
std::string const& dbname = request->databaseName();
AuthLevel level = AuthLevel::RW;
if (_authenticationEnabled &&
if (_authentication->isEnabled() &&
(!_authenticatedUser.empty() || !dbname.empty())) {
level = GeneralServerFeature::AUTH_INFO.canUseDatabase(
level = _authentication->authInfo()->canUseDatabase(
_authenticatedUser, dbname);
}

View File

@ -34,6 +34,9 @@
#include "lib/Rest/VppResponse.h"
namespace arangodb {
class AuthenticationFeature;
namespace rest {
class VppCommTask : public GeneralCommTask {
@ -62,7 +65,8 @@ class VppCommTask : public GeneralCommTask {
std::unique_ptr<GeneralResponse> createResponse(
rest::ResponseCode, uint64_t messageId) override final;
void handleAuthentication(VPackSlice const& header, uint64_t messageId);
void handleSimpleError(rest::ResponseCode code, uint64_t id) override {
VppResponse response(code, id);
addResponse(&response);
@ -132,6 +136,10 @@ class VppCommTask : public GeneralCommTask {
char const* vpackBegin, char const* chunkEnd);
std::string _authenticatedUser;
AuthenticationFeature* _authentication;
// user
// authenticated or not
// database aus url
bool _authenticationEnabled;
};
}

View File

@ -97,8 +97,13 @@ Syncer::Syncer(TRI_vocbase_t* vocbase,
std::string username = _configuration._username;
std::string password = _configuration._password;
_client->setUserNamePassword("/", username, password);
std::string jwt = _configuration._jwt;
if (!username.empty()) {
_client->setUserNamePassword("/", username, password);
} else {
_client->setJwt(_configuration._jwt);
}
_client->setLocationRewriter(this, &rewriteLocation);
_client->_maxRetries = 2;

View File

@ -27,7 +27,7 @@
#include <velocypack/velocypack-aliases.h>
#include "Basics/StringUtils.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Logger/Logger.h"
#include "Rest/HttpRequest.h"
#include "Ssl/SslInterface.h"
@ -38,22 +38,16 @@ using namespace arangodb::basics;
using namespace arangodb::rest;
RestAuthHandler::RestAuthHandler(GeneralRequest* request,
GeneralResponse* response,
std::string const* jwtSecret)
GeneralResponse* response)
: RestVocbaseBaseHandler(request, response),
_jwtSecret(*jwtSecret),
_validFor(60 * 60 * 24 * 30) {}
bool RestAuthHandler::isDirect() const { return false; }
std::string RestAuthHandler::generateJwt(std::string const& username,
std::string const& password) {
VPackBuilder headerBuilder;
{
VPackObjectBuilder h(&headerBuilder);
headerBuilder.add("alg", VPackValue("HS256"));
headerBuilder.add("typ", VPackValue("JWT"));
}
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>("Authentication");
TRI_ASSERT(authentication != nullptr);
std::chrono::seconds exp =
std::chrono::duration_cast<std::chrono::seconds>(
@ -66,15 +60,7 @@ std::string RestAuthHandler::generateJwt(std::string const& username,
bodyBuilder.add("iss", VPackValue("arangodb"));
bodyBuilder.add("exp", VPackValue(exp.count()));
}
std::string fullMessage(StringUtils::encodeBase64(headerBuilder.toJson()) +
"." +
StringUtils::encodeBase64(bodyBuilder.toJson()));
std::string signature =
sslHMAC(_jwtSecret.c_str(), _jwtSecret.length(), fullMessage.c_str(),
fullMessage.length(), SslInterface::Algorithm::ALGORITHM_SHA256);
return fullMessage + "." + StringUtils::encodeBase64U(signature);
return authentication->authInfo()->generateJwt(bodyBuilder);
}
RestStatus RestAuthHandler::execute() {
@ -109,9 +95,12 @@ RestStatus RestAuthHandler::execute() {
_username = usernameSlice.copyString();
std::string const password = passwordSlice.copyString();
auto authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
AuthResult auth =
GeneralServerFeature::AUTH_INFO.checkPassword(_username, password);
authentication->authInfo()->checkPassword(_username, password);
if (auth._authorized) {
VPackBuilder resultBuilder;

View File

@ -32,8 +32,7 @@
namespace arangodb {
class RestAuthHandler : public RestVocbaseBaseHandler {
public:
RestAuthHandler(GeneralRequest*, GeneralResponse*,
std::string const* jwtSecret);
RestAuthHandler(GeneralRequest*, GeneralResponse*);
std::string generateJwt(std::string const&, std::string const&);

View File

@ -3149,6 +3149,8 @@ void RestReplicationHandler::handleCommandMakeSlave() {
VelocyPackHelper::getStringValue(body, "username", "");
std::string const password =
VelocyPackHelper::getStringValue(body, "password", "");
std::string const jwt =
VelocyPackHelper::getStringValue(body, "jwt", "");
std::string const restrictType =
VelocyPackHelper::getStringValue(body, "restrictType", "");
@ -3162,6 +3164,7 @@ void RestReplicationHandler::handleCommandMakeSlave() {
config._database = database;
config._username = username;
config._password = password;
config._jwt = jwt;
config._includeSystem =
VelocyPackHelper::getBooleanValue(body, "includeSystem", true);
config._requestTimeout = VelocyPackHelper::getNumericValue<double>(
@ -3325,6 +3328,8 @@ void RestReplicationHandler::handleCommandSync() {
VelocyPackHelper::getStringValue(body, "username", "");
std::string const password =
VelocyPackHelper::getStringValue(body, "password", "");
std::string const jwt =
VelocyPackHelper::getStringValue(body, "jwt", "");
bool const verbose =
VelocyPackHelper::getBooleanValue(body, "verbose", false);
bool const includeSystem =
@ -3365,6 +3370,7 @@ void RestReplicationHandler::handleCommandSync() {
config._database = database;
config._username = username;
config._password = password;
config._jwt = jwt;
config._includeSystem = includeSystem;
config._verbose = verbose;
config._useCollectionId = useCollectionId;
@ -3480,6 +3486,11 @@ void RestReplicationHandler::handleCommandApplierSetConfig() {
if (password.isString()) {
config._password = password.copyString();
}
VPackSlice const jwt = body.get("jwt");
if (jwt.isString()) {
config._jwt = jwt.copyString();
}
config._requestTimeout = VelocyPackHelper::getNumericValue<double>(
body, "requestTimeout", config._requestTimeout);

View File

@ -37,7 +37,7 @@ class RestUploadHandler : public RestVocbaseBaseHandler {
~RestUploadHandler();
public:
RestStatus execute();
RestStatus execute() override;
char const* name() const override final { return "RestUploadHandler"; }
//////////////////////////////////////////////////////////////////////////////

View File

@ -34,7 +34,7 @@
#include "Basics/files.h"
#include "Cluster/ServerState.h"
#include "Cluster/v8-cluster.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Logger/Logger.h"
#include "ProgramOptions/ProgramOptions.h"
#include "ProgramOptions/Section.h"
@ -230,6 +230,7 @@ DatabaseFeature::DatabaseFeature(ApplicationServer* server)
_upgrade(false) {
setOptional(false);
requiresElevatedPrivileges(false);
startsAfter("Authentication");
startsAfter("DatabasePath");
startsAfter("EngineSelector");
startsAfter("LogfileManager");
@ -823,7 +824,9 @@ std::vector<std::string> DatabaseFeature::getDatabaseNamesForUser(
TRI_vocbase_t* vocbase = p.second;
TRI_ASSERT(vocbase != nullptr);
auto level = GeneralServerFeature::AUTH_INFO.canUseDatabase(
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
auto level = authentication->authInfo()->canUseDatabase(
username, vocbase->name());
if (level == AuthLevel::NONE) {

View File

@ -32,7 +32,7 @@
#include "Basics/tri-strings.h"
#include "Cluster/ServerState.h"
#include "Endpoint/ConnectionInfo.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Logger/Logger.h"
#include "Ssl/SslInterface.h"
#include "Utils/Events.h"
@ -46,36 +46,19 @@ using namespace arangodb::rest;
double VocbaseContext::ServerSessionTtl =
60.0 * 60.0 * 24 * 60; // 2 month session timeout
VocbaseContext::VocbaseContext(GeneralRequest* request, TRI_vocbase_t* vocbase,
std::string const& jwtSecret)
: RequestContext(request), _vocbase(vocbase), _jwtSecret(jwtSecret) {
VocbaseContext::VocbaseContext(GeneralRequest* request, TRI_vocbase_t* vocbase)
: RequestContext(request),
_vocbase(vocbase),
_authentication(nullptr) {
TRI_ASSERT(_vocbase != nullptr);
_authentication =
application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(_authentication != nullptr);
}
VocbaseContext::~VocbaseContext() { _vocbase->release(); }
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not to use special cluster authentication
////////////////////////////////////////////////////////////////////////////////
bool VocbaseContext::useClusterAuthentication() const {
auto role = ServerState::instance()->getRole();
if (ServerState::instance()->isDBServer(role)) {
return true;
}
if (ServerState::instance()->isCoordinator(role)) {
std::string const& s = _request->requestPath();
if (s == "/_api/shard-comm" || s == "/_admin/shutdown") {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief checks the authentication
////////////////////////////////////////////////////////////////////////////////
@ -83,11 +66,7 @@ bool VocbaseContext::useClusterAuthentication() const {
rest::ResponseCode VocbaseContext::authenticate() {
TRI_ASSERT(_vocbase != nullptr);
auto restServer =
application_features::ApplicationServer::getFeature<GeneralServerFeature>(
"GeneralServer");
if (!restServer->authentication()) {
if (!_authentication->isEnabled()) {
// no authentication required at all
return rest::ResponseCode::OK;
}
@ -108,16 +87,25 @@ rest::ResponseCode VocbaseContext::authenticate() {
forceOpen = true;
}
}
if (result != rest::ResponseCode::OK) {
return result;
}
std::string const& username = _request->user();
// mop: internal request => no username present
if (username.empty()) {
return rest::ResponseCode::OK;
}
// check that we are allowed to see the database
if (result == rest::ResponseCode::OK && !forceOpen) {
if (!forceOpen) {
if (!StringUtils::isPrefix(path, "/_api/user/")) {
std::string const& username = _request->user();
std::string const& dbname = _request->databaseName();
if (!username.empty() || !dbname.empty()) {
AuthLevel level =
GeneralServerFeature::AUTH_INFO.canUseDatabase(username, dbname);
_authentication->authInfo()->canUseDatabase(username, dbname);
if (level != AuthLevel::RW) {
events::NotAuthorized(_request);
@ -131,16 +119,13 @@ rest::ResponseCode VocbaseContext::authenticate() {
}
rest::ResponseCode VocbaseContext::authenticateRequest(bool* forceOpen) {
auto restServer =
application_features::ApplicationServer::getFeature<GeneralServerFeature>(
"GeneralServer");
#ifdef ARANGODB_HAVE_DOMAIN_SOCKETS
// check if we need to run authentication for this type of
// endpoint
ConnectionInfo const& ci = _request->connectionInfo();
if (ci.endpointType == Endpoint::DomainType::UNIX &&
!restServer->authenticationUnixSockets()) {
!_authentication->authenticationUnixSockets()) {
// no authentication required for unix socket domain connections
return rest::ResponseCode::OK;
}
@ -148,7 +133,7 @@ rest::ResponseCode VocbaseContext::authenticateRequest(bool* forceOpen) {
std::string const& path = _request->requestPath();
if (restServer->authenticationSystemOnly()) {
if (_authentication->authenticationSystemOnly()) {
// authentication required, but only for /_api, /_admin etc.
if (!path.empty()) {
@ -209,32 +194,7 @@ rest::ResponseCode VocbaseContext::authenticateRequest(bool* forceOpen) {
////////////////////////////////////////////////////////////////////////////////
rest::ResponseCode VocbaseContext::basicAuthentication(const char* auth) {
if (useClusterAuthentication()) {
std::string const expected = ServerState::instance()->getAuthentication();
if (expected.substr(6) != std::string(auth)) {
events::UnknownAuthenticationMethod(_request);
return rest::ResponseCode::UNAUTHORIZED;
}
std::string const up = StringUtils::decodeBase64(auth);
std::string::size_type n = up.find(':', 0);
if (n == std::string::npos || n == 0 || n + 1 > up.size()) {
LOG(TRACE) << "invalid authentication data found, cannot extract "
"username/password";
events::UnknownAuthenticationMethod(_request);
return rest::ResponseCode::BAD;
}
_request->setUser(up.substr(0, n));
events::Authenticated(_request, rest::AuthenticationMethod::BASIC);
return rest::ResponseCode::OK;
}
AuthResult result = GeneralServerFeature::AUTH_INFO.checkAuthentication(
AuthResult result = _authentication->authInfo()->checkAuthentication(
AuthInfo::AuthType::BASIC, auth);
_request->setUser(std::move(result._username));
@ -265,7 +225,7 @@ rest::ResponseCode VocbaseContext::basicAuthentication(const char* auth) {
////////////////////////////////////////////////////////////////////////////////
rest::ResponseCode VocbaseContext::jwtAuthentication(std::string const& auth) {
AuthResult result = GeneralServerFeature::AUTH_INFO.checkAuthentication(
AuthResult result = _authentication->authInfo()->checkAuthentication(
AuthInfo::AuthType::JWT, auth);
if (!result._authorized) {

View File

@ -32,6 +32,7 @@
#include "Rest/HttpRequest.h"
#include "Rest/HttpResponse.h"
#include "Rest/RequestContext.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Rest/GeneralRequest.h"
#include "Rest/GeneralResponse.h"
@ -44,7 +45,7 @@ class VocbaseContext : public arangodb::RequestContext {
static double ServerSessionTtl;
public:
VocbaseContext(GeneralRequest*, TRI_vocbase_t*, std::string const&);
VocbaseContext(GeneralRequest*, TRI_vocbase_t*);
~VocbaseContext();
public:
@ -53,9 +54,6 @@ class VocbaseContext : public arangodb::RequestContext {
public:
rest::ResponseCode authenticate() override final;
private:
bool useClusterAuthentication() const;
private:
//////////////////////////////////////////////////////////////////////////////
/// @brief checks the authentication (basic)
@ -78,7 +76,8 @@ class VocbaseContext : public arangodb::RequestContext {
private:
TRI_vocbase_t* _vocbase;
std::string const _jwtSecret;
AuthenticationFeature* _authentication;
};
}

View File

@ -42,6 +42,7 @@
#include "ApplicationFeatures/VersionFeature.h"
#include "Basics/ArangoGlobalContext.h"
#include "Cluster/ClusterFeature.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "Logger/LoggerBufferFeature.h"
#include "Logger/LoggerFeature.h"
@ -106,16 +107,17 @@ static int runServer(int argc, char** argv) {
application_features::ApplicationServer server(options, SBIN_DIRECTORY);
std::vector<std::string> nonServerFeatures = {
"Action", "Affinity", "Agency",
"Cluster", "Daemon", "Dispatcher",
"FoxxQueues", "GeneralServer", "LoggerBufferFeature",
"Server", "Scheduler", "SslServer",
"Statistics", "Supervisor"};
"Action", "Affinity", "Agency",
"Authentication", "Cluster", "Daemon",
"Dispatcher", "FoxxQueues", "GeneralServer",
"LoggerBufferFeature", "Server", "Scheduler",
"SslServer", "Statistics", "Supervisor"};
int ret = EXIT_FAILURE;
server.addFeature(new ActionFeature(&server));
server.addFeature(new AgencyFeature(&server));
server.addFeature(new AuthenticationFeature(&server));
server.addFeature(new BootstrapFeature(&server));
server.addFeature(new CheckVersionFeature(&server, &ret, nonServerFeatures));
server.addFeature(new ClusterFeature(&server));

View File

@ -27,7 +27,7 @@
#include "Basics/Common.h"
#include <boost/asio.hpp>
#include "Basics/asio-helper.h"
namespace arangodb {
namespace rest {

View File

@ -27,9 +27,9 @@
#include "Basics/Common.h"
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include "Basics/asio-helper.h"
#include "Basics/Mutex.h"
#include "Basics/MutexLocker.h"
#include "Basics/socket-utils.h"

View File

@ -26,7 +26,7 @@
#include "ApplicationFeatures/ApplicationFeature.h"
#include <boost/asio.hpp>
#include "Basics/asio-helper.h"
namespace arangodb {
namespace rest {

View File

@ -2635,12 +2635,14 @@ OperationResult Transaction::truncate(std::string const& collectionName,
/// @brief remove all documents in a collection, coordinator
////////////////////////////////////////////////////////////////////////////////
#ifndef USE_ENTERPRISE
OperationResult Transaction::truncateCoordinator(std::string const& collectionName,
OperationOptions& options) {
return OperationResult(
arangodb::truncateCollectionOnCoordinator(_vocbase->name(),
collectionName));
}
#endif
////////////////////////////////////////////////////////////////////////////////
/// @brief remove all documents in a collection, local

View File

@ -23,6 +23,8 @@
#include "v8-replication.h"
#include "Basics/ReadLocker.h"
#include "Cluster/ClusterComm.h"
#include "Cluster/ClusterFeature.h"
#include "Replication/InitialSyncer.h"
#include "Rest/Version.h"
#include "RestServer/ServerIdFeature.h"
@ -185,6 +187,33 @@ static void JS_LastLoggerReplication(
TRI_V8_TRY_CATCH_END
}
void addReplicationAuthentication(v8::Isolate* isolate,
v8::Handle<v8::Object> object,
TRI_replication_applier_configuration_t &config) {
bool hasUsernamePassword = false;
if (object->Has(TRI_V8_ASCII_STRING("username"))) {
if (object->Get(TRI_V8_ASCII_STRING("username"))->IsString()) {
hasUsernamePassword = true;
config._username =
TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("username")));
}
}
if (object->Has(TRI_V8_ASCII_STRING("password"))) {
if (object->Get(TRI_V8_ASCII_STRING("password"))->IsString()) {
hasUsernamePassword = true;
config._password =
TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("password")));
}
}
if (!hasUsernamePassword) {
auto cluster = application_features::ApplicationServer::getFeature<ClusterFeature>("Cluster");
if (cluster->isEnabled()) {
config._jwt = ClusterComm::instance()->jwt();
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sync data from a remote master
////////////////////////////////////////////////////////////////////////////////
@ -219,15 +248,6 @@ static void JS_SynchronizeReplication(
database = vocbase->name();
}
std::string username;
if (object->Has(TRI_V8_ASCII_STRING("username"))) {
username = TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("username")));
}
std::string password;
if (object->Has(TRI_V8_ASCII_STRING("password"))) {
password = TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("password")));
}
std::unordered_map<std::string, bool> restrictCollections;
if (object->Has(TRI_V8_ASCII_STRING("restrictCollections")) &&
@ -273,8 +293,8 @@ static void JS_SynchronizeReplication(
TRI_replication_applier_configuration_t config;
config._endpoint = endpoint;
config._database = database;
config._username = username;
config._password = password;
addReplicationAuthentication(isolate, object, config);
if (object->Has(TRI_V8_ASCII_STRING("chunkSize"))) {
if (object->Get(TRI_V8_ASCII_STRING("chunkSize"))->IsNumber()) {
@ -457,20 +477,7 @@ static void JS_ConfigureApplierReplication(
config._database = vocbase->name();
}
}
if (object->Has(TRI_V8_ASCII_STRING("username"))) {
if (object->Get(TRI_V8_ASCII_STRING("username"))->IsString()) {
config._username =
TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("username")));
}
}
if (object->Has(TRI_V8_ASCII_STRING("password"))) {
if (object->Get(TRI_V8_ASCII_STRING("password"))->IsString()) {
config._password =
TRI_ObjectToString(object->Get(TRI_V8_ASCII_STRING("password")));
}
}
addReplicationAuthentication(isolate, object, config);
if (object->Has(TRI_V8_ASCII_STRING("requestTimeout"))) {
if (object->Get(TRI_V8_ASCII_STRING("requestTimeout"))->IsNumber()) {

View File

@ -52,6 +52,7 @@
#include "Cluster/ClusterInfo.h"
#include "Cluster/ClusterMethods.h"
#include "Cluster/ServerState.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "GeneralServer/GeneralServerFeature.h"
#include "ReadCache/GlobalRevisionCache.h"
#include "Rest/Version.h"
@ -939,8 +940,12 @@ static void JS_ReloadAuth(v8::FunctionCallbackInfo<v8::Value> const& args) {
if (args.Length() != 0) {
TRI_V8_THROW_EXCEPTION_USAGE("RELOAD_AUTH()");
}
GeneralServerFeature::AUTH_INFO.outdate();
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
if (authentication->isEnabled()) {
authentication->authInfo()->outdate();
}
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
@ -2639,6 +2644,11 @@ static void JS_TrustedProxies(v8::FunctionCallbackInfo<v8::Value> const& args) {
static void JS_AuthenticationEnabled(
v8::FunctionCallbackInfo<v8::Value> const& args) {
auto authentication = application_features::ApplicationServer::getFeature<AuthenticationFeature>(
"Authentication");
TRI_ASSERT(authentication != nullptr);
// mop: one could argue that this is a function because this might be
// changable on the fly at some time but the sad truth is server startup
// order
@ -2647,7 +2657,7 @@ static void JS_AuthenticationEnabled(
v8::HandleScope scope(isolate);
v8::Handle<v8::Boolean> result =
v8::Boolean::New(isolate, GeneralServerFeature::authenticationEnabled());
v8::Boolean::New(isolate, authentication->isEnabled());
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END

View File

@ -23,12 +23,11 @@
#include "AuthInfo.h"
#include <chrono>
#include <velocypack/Builder.h>
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
#include "Aql/Query.h"
#include "Basics/ReadLocker.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/WriteLocker.h"
@ -142,6 +141,16 @@ AuthLevel AuthEntry::canUseDatabase(std::string const& dbname) const {
return it->second;
}
void AuthInfo::setJwtSecret(std::string const& jwtSecret) {
WRITE_LOCKER(writeLocker, _authJwtLock);
_jwtSecret = jwtSecret;
_authJwtCache.clear();
}
std::string AuthInfo::jwtSecret() {
return _jwtSecret;
}
void AuthInfo::clear() {
_authInfo.clear();
_authBasicCache.clear();
@ -221,31 +230,41 @@ void AuthInfo::reload() {
<< "and authorization information";
return;
}
MUTEX_LOCKER(locker, _queryLock);
if (!_outdated) {
return;
}
std::string queryStr("FOR user IN _users RETURN user");
auto nullBuilder = std::make_shared<VPackBuilder>();
VPackBuilder options;
{
VPackObjectBuilder b(&options);
}
auto objectBuilder = std::make_shared<VPackBuilder>(options);
arangodb::aql::Query query(false, vocbase, queryStr.c_str(),
queryStr.length(), nullBuilder, objectBuilder,
arangodb::aql::PART_MAIN);
LOG(DEBUG) << "starting to load authentication and authorization information";
SingleCollectionTransaction trx(StandaloneTransactionContext::Create(vocbase),
TRI_COL_NAME_USERS, TRI_TRANSACTION_READ);
int res = trx.begin();
if (res != TRI_ERROR_NO_ERROR) {
LOG(ERR) << "cannot start transaction to load authentication";
TRI_ASSERT(_queryRegistry != nullptr);
auto queryResult = query.execute(_queryRegistry);
if (queryResult.code != TRI_ERROR_NO_ERROR) {
if (queryResult.code == TRI_ERROR_REQUEST_CANCELED ||
(queryResult.code == TRI_ERROR_QUERY_KILLED)) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED);
}
_outdated = false;
return;
}
VPackSlice usersSlice = queryResult.result->slice();
OperationResult users =
trx.all(TRI_COL_NAME_USERS, 0, UINT64_MAX, OperationOptions());
trx.finish(users.code);
if (users.failed()) {
LOG(ERR) << "cannot read users from _users collection";
return;
if (usersSlice.isNone()) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
auto usersSlice = users.slice();
if (!usersSlice.isArray()) {
LOG(ERR) << "cannot read users from _users collection";
return;
@ -401,41 +420,63 @@ AuthResult AuthInfo::checkAuthenticationBasic(std::string const& secret) {
return result;
}
AuthResult AuthInfo::checkAuthenticationJWT(std::string const& secret) {
std::vector<std::string> const parts = StringUtils::split(secret, '.');
AuthResult AuthInfo::checkAuthenticationJWT(std::string const& jwt) {
try {
READ_LOCKER(readLocker, _authJwtLock);
auto result = _authJwtCache.get(jwt);
if (result._expires) {
std::chrono::system_clock::time_point now =
std::chrono::system_clock::now();
if (parts.size() != 3) {
LOG(DEBUG) << "Secret contains " << parts.size() << " parts";
return AuthResult();
if (now >= result._expireTime) {
readLocker.unlock();
WRITE_LOCKER(writeLocker, _authJwtLock);
result = _authJwtCache.get(jwt);
if (result._expires && now >= result._expireTime) {
try {
_authJwtCache.remove(jwt);
} catch (std::range_error const& e) {
}
}
return AuthResult();
}
}
return (AuthResult) result;
} catch (std::range_error const& e) {
// mop: not found
}
std::vector<std::string> const parts = StringUtils::split(jwt, '.');
if (parts.size() != 3) {
LOG(TRACE) << "Secret contains " << parts.size() << " parts";
return AuthResult();
}
std::string const& header = parts[0];
std::string const& body = parts[1];
std::string const& signature = parts[2];
if (!validateJwtHeader(header)) {
LOG(TRACE) << "Couldn't validate jwt header " << header;
return AuthResult();
}
AuthJwtResult result = validateJwtBody(body);
if (!result._authorized) {
LOG(TRACE) << "Couldn't validate jwt body " << body;
return AuthResult();
}
std::string const message = header + "." + body;
if (!validateJwtHeader(header)) {
LOG(DEBUG) << "Couldn't validate jwt header " << header;
return AuthResult();
}
std::string username;
if (!validateJwtBody(body, &username)) {
LOG(DEBUG) << "Couldn't validate jwt body " << body;
return AuthResult();
}
if (!validateJwtHMAC256Signature(message, signature)) {
LOG(DEBUG) << "Couldn't validate jwt signature " << signature;
LOG(TRACE) << "Couldn't validate jwt signature " << signature << " " << _jwtSecret;
return AuthResult();
}
AuthResult result;
result._username = username;
result._authorized = true;
return result;
WRITE_LOCKER(writeLocker, _authJwtLock);
_authJwtCache.put(jwt, result);
return (AuthResult) result;
}
std::shared_ptr<VPackBuilder> AuthInfo::parseJson(std::string const& str,
@ -491,40 +532,47 @@ bool AuthInfo::validateJwtHeader(std::string const& header) {
return true;
}
bool AuthInfo::validateJwtBody(std::string const& body, std::string* username) {
AuthJwtResult AuthInfo::validateJwtBody(std::string const& body) {
std::shared_ptr<VPackBuilder> bodyBuilder =
parseJson(StringUtils::decodeBase64(body), "jwt body");
AuthJwtResult authResult;
if (bodyBuilder.get() == nullptr) {
return false;
return authResult;
}
VPackSlice const bodySlice = bodyBuilder->slice();
if (!bodySlice.isObject()) {
return false;
return authResult;
}
VPackSlice const issSlice = bodySlice.get("iss");
if (!issSlice.isString()) {
return false;
return authResult;
}
if (issSlice.copyString() != "arangodb") {
return false;
return authResult;
}
VPackSlice const usernameSlice = bodySlice.get("preferred_username");
if (!usernameSlice.isString()) {
return false;
if (bodySlice.hasKey("preferred_username")) {
VPackSlice const usernameSlice = bodySlice.get("preferred_username");
if (!usernameSlice.isString()) {
return authResult;
}
authResult._username = usernameSlice.copyString();
} else if (bodySlice.hasKey("server_id")) {
// mop: hmm...nothing to do here :D
// authResult._username = "root";
} else {
return authResult;
}
*username = usernameSlice.copyString();
// mop: optional exp (cluster currently uses non expiring jwts)
if (bodySlice.hasKey("exp")) {
VPackSlice const expSlice = bodySlice.get("exp");
if (!expSlice.isNumber()) {
return false;
return authResult;
}
std::chrono::system_clock::time_point expires(
@ -533,19 +581,67 @@ bool AuthInfo::validateJwtBody(std::string const& body, std::string* username) {
std::chrono::system_clock::now();
if (now >= expires) {
return false;
return authResult;
}
authResult._expires = true;
authResult._expireTime = expires;
}
return true;
authResult._authorized = true;
return authResult;
}
bool AuthInfo::validateJwtHMAC256Signature(std::string const& message,
std::string const& signature) {
std::string decodedSignature = StringUtils::decodeBase64U(signature);
std::string const& jwtSecret = GeneralServerFeature::getJwtSecret();
return verifyHMAC(jwtSecret.c_str(), jwtSecret.length(), message.c_str(),
return verifyHMAC(_jwtSecret.c_str(), _jwtSecret.length(), message.c_str(),
message.length(), decodedSignature.c_str(),
decodedSignature.length(),
SslInterface::Algorithm::ALGORITHM_SHA256);
}
std::string AuthInfo::generateRawJwt(VPackBuilder const& bodyBuilder) {
VPackBuilder headerBuilder;
{
VPackObjectBuilder h(&headerBuilder);
headerBuilder.add("alg", VPackValue("HS256"));
headerBuilder.add("typ", VPackValue("JWT"));
}
std::string fullMessage(StringUtils::encodeBase64(headerBuilder.toJson()) +
"." +
StringUtils::encodeBase64(bodyBuilder.toJson()));
std::string signature =
sslHMAC(_jwtSecret.c_str(), _jwtSecret.length(), fullMessage.c_str(),
fullMessage.length(), SslInterface::Algorithm::ALGORITHM_SHA256);
return fullMessage + "." + StringUtils::encodeBase64U(signature);
}
std::string AuthInfo::generateJwt(VPackBuilder const& payload) {
if (!payload.slice().isObject()) {
std::string error = "Need an object to generate a JWT. Got: ";
error += payload.slice().typeName();
throw std::runtime_error(error);
}
bool hasIss = payload.slice().hasKey("iss");
bool hasIat = payload.slice().hasKey("iat");
VPackBuilder bodyBuilder;
if (hasIss && hasIat) {
bodyBuilder = payload;
} else {
VPackObjectBuilder p(&bodyBuilder);
if (!hasIss) {
bodyBuilder.add("iss", VPackValue("arangodb"));
}
if (!hasIat) {
bodyBuilder.add("iat", VPackValue(TRI_microtime() / 1000));
}
for (auto const& obj : VPackObjectIterator(payload.slice())) {
bodyBuilder.add(obj.key.copyString(), obj.value);
}
}
return generateRawJwt(bodyBuilder);
}

View File

@ -26,10 +26,14 @@
#include "Basics/Common.h"
#include <chrono>
#include <velocypack/Builder.h>
#include <velocypack/velocypack-aliases.h>
#include "Aql/QueryRegistry.h"
#include "Basics/ReadWriteLock.h"
#include "Basics/LruCache.h"
namespace arangodb {
namespace velocypack {
@ -90,6 +94,13 @@ class AuthResult {
bool _mustChange;
};
class AuthJwtResult: public AuthResult {
public:
AuthJwtResult() : AuthResult(), _expires(false) {}
bool _expires;
std::chrono::system_clock::time_point _expireTime;
};
class AuthInfo {
public:
enum class AuthType {
@ -97,9 +108,18 @@ class AuthInfo {
};
public:
AuthInfo() : _outdated(true) {}
AuthInfo()
: _outdated(true),
_authJwtCache(16384),
_jwtSecret(""),
_queryRegistry(nullptr) {
}
public:
void setQueryRegistry(aql::QueryRegistry* registry) {
TRI_ASSERT(registry != nullptr);
_queryRegistry = registry;
};
void outdate() { _outdated = true; }
AuthResult checkPassword(std::string const& username,
@ -110,7 +130,12 @@ class AuthInfo {
AuthLevel canUseDatabase(std::string const& username,
std::string const& dbname);
void setJwtSecret(std::string const&);
std::string jwtSecret();
std::string generateJwt(VPackBuilder const&);
std::string generateRawJwt(VPackBuilder const&);
private:
void reload();
void clear();
@ -120,16 +145,21 @@ class AuthInfo {
AuthResult checkAuthenticationBasic(std::string const& secret);
AuthResult checkAuthenticationJWT(std::string const& secret);
bool validateJwtHeader(std::string const&);
bool validateJwtBody(std::string const&, std::string*);
AuthJwtResult validateJwtBody(std::string const&);
bool validateJwtHMAC256Signature(std::string const&, std::string const&);
std::shared_ptr<VPackBuilder> parseJson(std::string const&, std::string const&);
private:
basics::ReadWriteLock _authInfoLock;
basics::ReadWriteLock _authJwtLock;
Mutex _queryLock;
std::atomic<bool> _outdated;
std::unordered_map<std::string, arangodb::AuthEntry> _authInfo;
std::unordered_map<std::string, arangodb::AuthResult> _authBasicCache;
arangodb::basics::LruCache<std::string, arangodb::AuthJwtResult> _authJwtCache;
std::string _jwtSecret;
aql::QueryRegistry* _queryRegistry;
};
}

View File

@ -128,17 +128,26 @@ static int LoadConfiguration(TRI_vocbase_t* vocbase,
// read username / password
value = slice.get("username");
bool hasUsernamePassword = false;
if (value.isString()) {
hasUsernamePassword = true;
config->_username = value.copyString();
}
value = slice.get("password");
if (value.isString()) {
hasUsernamePassword = true;
config->_password = value.copyString();
}
if (!hasUsernamePassword) {
value = slice.get("jwt");
if (value.isString()) {
config->_jwt = value.copyString();
}
}
value = slice.get("requestTimeout");
if (value.isNumber()) {
@ -445,6 +454,7 @@ TRI_replication_applier_configuration_t::
_database(),
_username(),
_password(),
_jwt(),
_requestTimeout(600.0),
_connectTimeout(10.0),
_ignoreErrors(0),
@ -489,12 +499,19 @@ void TRI_replication_applier_configuration_t::toVelocyPack(
if (!_database.empty()) {
builder.add("database", VPackValue(_database));
}
bool hasUsernamePassword = false;
if (!_username.empty()) {
hasUsernamePassword = true;
builder.add("username", VPackValue(_username));
}
if (includePassword) {
hasUsernamePassword = true;
builder.add("password", VPackValue(_password));
}
if (!hasUsernamePassword && !_jwt.empty()) {
builder.add("jwt", VPackValue(_jwt));
}
builder.add("requestTimeout", VPackValue(_requestTimeout));
builder.add("connectTimeout", VPackValue(_connectTimeout));
@ -789,6 +806,7 @@ void TRI_replication_applier_configuration_t::update(
_database = src->_database;
_username = src->_username;
_password = src->_password;
_jwt = src->_jwt;
_requestTimeout = src->_requestTimeout;
_connectTimeout = src->_connectTimeout;
_ignoreErrors = src->_ignoreErrors;

View File

@ -46,6 +46,7 @@ class TRI_replication_applier_configuration_t {
std::string _database;
std::string _username;
std::string _password;
std::string _jwt;
double _requestTimeout;
double _connectTimeout;
uint64_t _ignoreErrors;

View File

@ -48,10 +48,12 @@ module.exports = router;
router.get('/config.js', function (req, res) {
const scriptName = req.get('x-script-name');
const basePath = req.trustProxy && scriptName || '';
const isEnterprise = internal.isEnterprise();
res.send(
`var frontendConfig = ${JSON.stringify({
basePath: basePath,
db: req.database,
isEnterprise: isEnterprise,
authenticationEnabled: internal.authenticationEnabled(),
isCluster: cluster.isCluster()
})}`

File diff suppressed because one or more lines are too long

View File

@ -1005,6 +1005,7 @@ if (list.length > 0) {
</div>
</div> <% graphs.forEach(function(graph) {
var graphName = graph.get("_key");
var isSmart = graph.get("isSmart");
%> <div class="tile tile-graph pure-u-1-1 pure-u-sm-1-2 pure-u-md-1-3 pure-u-lg-1-4 pure-u-xl-1-6" id="<%=graphName %>_tile">
<div class="paddingBox">
<div class="borderBox"></div>
@ -1013,7 +1014,7 @@ if (list.length > 0) {
</div>
<span class="icon_arangodb_edge5 tile-icon"></span>
<span class="icon_arangodb_edge5 icon_arangodb_edge5-2 tile-icon"></span>
<div class="tileBadge"></div>
<div class="tileBadge"> <% if (isSmart === true) { %> <span><div class="corneredBadge inProgress">Smart</div></span> <% } %> </div>
<h5 class="collectionName"><%=graphName %></h5>
</div>
</div> <%});%> </div>
@ -2740,4 +2741,4 @@ var cutByResolution = function (str) {
</div>
<div id="workMonitorContent" class="innerContent">
</div></script></head><body><nav class="navbar" style="display: none"><div class="primary"><div class="navlogo"><a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb_logo_big.svg"></a><a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"></a><a class="version"><span>VERSION: </span><span id="currentVersion"></span></a></div><div class="statmenu" id="statisticBar"></div><div class="navmenu" id="navigationBar"></div></div></nav><div id="modalPlaceholder"></div><div class="bodyWrapper" style="display: none"><div class="centralRow"><div id="navbar2" class="navbarWrapper secondary"><div class="subnavmenu" id="subNavigationBar"></div></div><div class="resizecontainer contentWrapper"><div id="loadingScreen" class="loadingScreen" style="display: none"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw margin-bottom"></i> <span class="sr-only">Loading...</span></div><div id="content" class="centralContent"></div><footer class="footer"><div id="footerBar"></div></footer></div></div></div><div id="progressPlaceholder" style="display:none"></div><div id="spotlightPlaceholder" style="display:none"></div><div id="graphSettingsContent" style="display: none"></div><div id="offlinePlaceholder" style="display:none"><div class="offline-div"><div class="pure-u"><div class="pure-u-1-4"></div><div class="pure-u-1-2 offline-window"><div class="offline-header"><h3>You have been disconnected from the server</h3></div><div class="offline-body"><p>The connection to the server has been lost. The server may be under heavy load.</p><p>Trying to reconnect in <span id="offlineSeconds">10</span> seconds.</p><p class="animation_state"><span><button class="button-success">Reconnect now</button></span></p></div></div><div class="pure-u-1-4"></div></div></div></div><div class="arangoFrame" style=""><div class="outerDiv"><div class="innerDiv"></div></div></div><script src="libs.js?version=1476457280679"></script><script src="app.js?version=1476457280679"></script></body></html>
</div></script></head><body><nav class="navbar" style="display: none"><div class="primary"><div class="navlogo"><a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb_logo_big.svg"></a><a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"></a><a class="version"><span>VERSION: </span><span id="currentVersion"></span></a></div><div class="statmenu" id="statisticBar"></div><div class="navmenu" id="navigationBar"></div></div></nav><div id="modalPlaceholder"></div><div class="bodyWrapper" style="display: none"><div class="centralRow"><div id="navbar2" class="navbarWrapper secondary"><div class="subnavmenu" id="subNavigationBar"></div></div><div class="resizecontainer contentWrapper"><div id="loadingScreen" class="loadingScreen" style="display: none"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw margin-bottom"></i> <span class="sr-only">Loading...</span></div><div id="content" class="centralContent"></div><footer class="footer"><div id="footerBar"></div></footer></div></div></div><div id="progressPlaceholder" style="display:none"></div><div id="spotlightPlaceholder" style="display:none"></div><div id="graphSettingsContent" style="display: none"></div><div id="offlinePlaceholder" style="display:none"><div class="offline-div"><div class="pure-u"><div class="pure-u-1-4"></div><div class="pure-u-1-2 offline-window"><div class="offline-header"><h3>You have been disconnected from the server</h3></div><div class="offline-body"><p>The connection to the server has been lost. The server may be under heavy load.</p><p>Trying to reconnect in <span id="offlineSeconds">10</span> seconds.</p><p class="animation_state"><span><button class="button-success">Reconnect now</button></span></p></div></div><div class="pure-u-1-4"></div></div></div></div><div class="arangoFrame" style=""><div class="outerDiv"><div class="innerDiv"></div></div></div><script src="libs.js?version=1476701130414"></script><script src="app.js?version=1476701130414"></script></body></html>

View File

@ -138,15 +138,6 @@
var currentVersion =
window.versionHelper.fromString(data.version);
if (data.license) {
window.frontendConfig.license = data.license;
if (data.license !== 'community') {
$('#ArangoDBLogo').attr('src', 'img/arangodb_logo_alt.svg');
} else {
$('.enterprise-menu').show();
}
}
$('.navbar #currentVersion').html(
' ' + data.version.substr(0, 5) + '<i class="fa fa-exclamation-circle"></i>'
);

View File

@ -201,6 +201,82 @@
});
},
toggleSmartGraph: function () {
var i;
var self = this;
if ($('#new-is_smart').is(':checked') === true) {
for (i = 0; i < this.counter; i++) {
$('#newEdgeDefinitions' + i).select2({
tags: []
});
self.cachedNewEdgeDefinitions = $('#newEdgeDefinitions' + i).select2('data');
self.cachedNewEdgeDefinitionsState = $('#newEdgeDefinitions' + i).attr('disabled');
$('#newEdgeDefinitions' + i).select2('data', '');
$('#newEdgeDefinitions' + i).attr('disabled', false);
$('#fromCollections' + i).select2({
tags: []
});
self.cachedFromCollections = $('#fromCollections' + i).select2('data');
self.cachedFromCollectionsState = $('#fromCollections' + i).attr('disabled');
$('#fromCollections' + i).select2('data', '');
$('#fromCollections' + i).attr('disabled', false);
$('#toCollections' + i).select2({
tags: []
});
self.cachedToCollections = $('#toCollections' + i).select2('data');
self.cachedToCollectionsState = $('#toCollections' + i).attr('disabled');
$('#toCollections' + i).select2('data', '');
$('#toCollections' + i).attr('disabled', false);
$('#newVertexCollections' + i).select2({
tags: []
});
self.cachedNewVertexCollections = $('#newVertexCollections' + i).select2('data');
self.cachedNewVertexCollectionsState = $('#newVertexCollections' + i).attr('disabled');
$('#newVertexCollections' + i).select2('data', '');
$('#newVertexCollections' + i).attr('disabled', false);
}
} else {
var collList = []; var collections = this.options.collectionCollection.models;
collections.forEach(function (c) {
if (c.get('isSystem')) {
return;
}
collList.push(c.id);
});
for (i = 0; i < this.counter; i++) {
$('#newEdgeDefinitions' + i).select2({
tags: this.eCollList
});
$('#newEdgeDefinitions' + i).select2('data', self.cachedNewEdgeDefinitions);
$('#newEdgeDefinitions' + i).attr('disabled', self.cachedNewEdgeDefinitionsState);
$('#fromCollections' + i).select2({
tags: collList
});
$('#fromCollections' + i).select2('data', self.cachedFromCollections);
$('#fromCollections' + i).attr('disabled', self.cachedFromCollectionsState);
$('#toCollections' + i).select2({
tags: collList
});
$('#toCollections' + i).select2('data', self.cachedToCollections);
$('#toCollections' + i).attr('disabled', self.cachedToCollectionsState);
$('#newVertexCollections' + i).select2({
tags: collList
});
$('#newVertexCollections' + i).select2('data', self.cachedNewVertexCollections);
$('#newVertexCollections' + i).attr('disabled', self.cachedNewVertexCollectionsState);
}
}
},
render: function (name, refetch) {
var self = this;
this.collection.fetch({
@ -226,6 +302,7 @@
self.events['click .graphViewer-icon-button'] = self.addRemoveDefinition.bind(self);
self.events['click #graphTab a'] = self.toggleTab.bind(self);
self.events['click .createExampleGraphs'] = self.createExampleGraphs.bind(self);
self.events['click #new-is_smart'] = self.toggleSmartGraph.bind(self);
self.events['focusout .select2-search-field input'] = function (e) {
if ($('.select2-drop').is(':visible')) {
if (!$('#select2-search-field input').is(':focus')) {
@ -733,7 +810,7 @@
)
);
if (window.frontendConfig.license === 'enterprise') {
if (window.frontendConfig.isEnterprise === false) {
var advanced = {};
var advancedTableContent = [];
@ -742,7 +819,7 @@
'new-is_smart',
'Smart Graph',
true,
'Do you want to create a smart graph?',
'Create a Smart Graph? Edge and vertex collections will be automatically generated. They are not allowed to be present before graph creation.',
false
)
);

View File

@ -107,6 +107,13 @@
self.resize();
console.log(window.frontendConfig);
if (window.frontendConfig.isEnterprise === true) {
$('#ArangoDBLogo').attr('src', 'img/arangodb_logo_alt.svg');
} else {
$('.enterprise-menu').show();
}
return this;
},

View File

@ -303,6 +303,11 @@ global.DEFINE_MODULE('internal', (function () {
exports.download = global.SYS_DOWNLOAD;
delete global.SYS_DOWNLOAD;
}
if (global.SYS_CLUSTER_DOWNLOAD) {
exports.clusterDownload = global.SYS_CLUSTER_DOWNLOAD;
delete global.SYS_CLUSTER_DOWNLOAD;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief whether or not Statistics are enabled

View File

@ -124,30 +124,11 @@ ArangoCollection.prototype.toArray = function () {
ArangoCollection.prototype.truncate = function () {
var cluster = require('@arangodb/cluster');
if (cluster.isCoordinator()) {
if (this.status() === ArangoCollection.STATUS_UNLOADED) {
this.load();
}
var dbName = require('internal').db._name();
var shards = cluster.shardList(dbName, this.name());
var coord = { coordTransactionID: ArangoClusterComm.getId() };
var options = { coordTransactionID: coord.coordTransactionID, timeout: 360 };
shards.forEach(function (shard) {
ArangoClusterComm.asyncRequest('put',
'shard:' + shard,
dbName,
'/_api/collection/' + encodeURIComponent(shard) + '/truncate',
'',
{ },
options);
});
cluster.wait(coord, shards.length);
return;
}
return this.TRUNCATE();
};

View File

@ -33,7 +33,7 @@ var arangodb = require('@arangodb');
var ArangoCollection = arangodb.ArangoCollection;
var ArangoError = arangodb.ArangoError;
var errors = require("internal").errors;
var request = require('@arangodb/request').request;
var request = require('@arangodb/request').clusterRequest;
var wait = require('internal').wait;
var _ = require('lodash');
@ -1119,7 +1119,7 @@ function synchronizeOneShard (database, shard, planId, leader) {
shard, 300);
console.debug('lockJobId:', lockJobId);
} catch (err1) {
console.error('synchronizeOneShard: exception in startReadLockOnLeader:', err1);
console.error('synchronizeOneShard: exception in startReadLockOnLeader:', err1, err1.stack);
}
finally {
cancelBarrier(ep, database, sy.barrierId);

View File

@ -29,7 +29,12 @@
var internal = require('internal');
var endpointToURL = require('@arangodb/cluster').endpointToURL;
var request = require('@arangodb/request').request;
var request;
if (ArangoServerState.role() == 'PRIMARY') {
request = require('@arangodb/request').clusterRequest;
} else {
request = require('@arangodb/request').request;
}
var logger = { };
var applier = { };

View File

@ -0,0 +1,155 @@
/* jshint sub: true */
/* global exports: true */
'use strict';
// //////////////////////////////////////////////////////////////////////////////
// / @brief node-request-style HTTP requests
// /
// / @file
// /
// / DISCLAIMER
// /
// / Copyright 2015 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 Andreas Streichardt <andreas@arangodb.com>
// / @author Copyright 2016, ArangoDB GmbH, Cologne, Germany
// //////////////////////////////////////////////////////////////////////////////
const internal = require('internal');
const Buffer = require('buffer').Buffer;
const extend = require('lodash').extend;
const url = require('url');
const is = require('@arangodb/is');
const httperr = require('http-errors');
const querystring = require('querystring');
const qs = require('qs');
let request = require('../../../common/modules/@arangodb/request.js');
let Response = request.Response;
exports = request;
function querystringify (query, useQuerystring) {
if (!query) {
return '';
}
if (typeof query === 'string') {
return query.charAt(0) === '?' ? query.slice(1) : query;
}
return (useQuerystring ? querystring : qs).stringify(query)
.replace(/[!'()*]/g, function (c) {
// Stricter RFC 3986 compliance
return '%' + c.charCodeAt(0).toString(16);
});
}
function clusterRequest(req) {
if (typeof req === 'string') {
req = {url: req, method: 'GET'};
}
let path = req.url || req.uri;
if (!path) {
throw new Error('Request URL must not be empty.');
}
let pathObj = typeof path === 'string' ? url.parse(path) : path;
if (pathObj.auth) {
let auth = pathObj.auth.split(':');
req = extend({
auth: {
username: decodeURIComponent(auth[0]),
password: decodeURIComponent(auth[1])
}
}, req);
delete pathObj.auth;
}
let query = typeof req.qs === 'string' ? req.qs : querystringify(req.qs, req.useQuerystring);
if (query) {
pathObj.search = query;
}
path = url.format(pathObj);
let contentType;
let body = req.body;
if (req.json) {
body = JSON.stringify(body);
contentType = 'application/json';
} else if (typeof body === 'string') {
contentType = 'text/plain; charset=utf-8';
} else if (typeof body === 'object' && body instanceof Buffer) {
contentType = 'application/octet-stream';
} else if (!body) {
if (req.form) {
contentType = 'application/x-www-form-urlencoded';
body = typeof req.form === 'string' ? req.form : querystringify(req.form, req.useQuerystring);
} else if (req.formData) {
// contentType = 'multipart/form-data'
// body = formData(req.formData)
throw new Error('Multipart form encoding is currently not supported.');
} else if (req.multipart) {
// contentType = 'multipart/related'
// body = multipart(req.multipart)
throw new Error('Multipart encoding is currently not supported.');
}
}
const headers = {};
if (contentType) {
headers['content-type'] = contentType;
}
if (req.headers) {
Object.keys(req.headers).forEach(function (name) {
headers[name.toLowerCase()] = req.headers[name];
});
}
if (req.auth) {
headers['authorization'] = ( // eslint-disable-line dot-notation
req.auth.bearer ?
'Bearer ' + req.auth.bearer :
'Basic ' + new Buffer(
req.auth.username + ':' +
req.auth.password
).toString('base64')
);
}
let options = {
method: (req.method || 'get').toUpperCase(),
headers: headers,
returnBodyAsBuffer: true,
returnBodyOnError: req.returnBodyOnError !== false
};
if (is.existy(req.timeout)) {
options.timeout = req.timeout;
}
if (is.existy(req.followRedirect)) {
options.followRedirects = req.followRedirect; // [sic] node-request compatibility
}
if (is.existy(req.maxRedirects)) {
options.maxRedirects = req.maxRedirects;
} else {
options.maxRedirects = 10;
}
let result = internal.clusterDownload(path, body, options);
return new Response(result, req.encoding, req.json);
}
exports.clusterRequest = clusterRequest;
module.exports = exports;

133
lib/Basics/LruCache.h Normal file
View File

@ -0,0 +1,133 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 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 ArangoDB GmbH, Cologne, Germany
///
/// Portions of the code are:
///
/// Copyright (c) 2014, lamerman
/// All rights reserved.
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions are met:
///
/// * Redistributions of source code must retain the above copyright notice, this
/// list of conditions and the following disclaimer.
///
/// * Redistributions in binary form must reproduce the above copyright notice,
/// this list of conditions and the following disclaimer in the documentation
/// and/or other materials provided with the distribution.
///
/// * Neither the name of lamerman nor the names of its
/// contributors may be used to endorse or promote products derived from
/// this software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
/// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
/// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
/// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
/// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
/// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
/// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
/// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///
//////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGODB_LRUCACHE_H
#define ARANGODB_LRUCACHE_H 1
#include <unordered_map>
#include <list>
#include <cstddef>
#include <stdexcept>
namespace arangodb {
namespace basics {
template<typename key_t, typename value_t>
class LruCache {
public:
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
LruCache(size_t max_size) :
_max_size(max_size) {
}
void put(const key_t& key, const value_t& value) {
auto it = _cache_items_map.find(key);
_cache_items_list.push_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end()) {
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();
if (_cache_items_map.size() > _max_size) {
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
}
const value_t& get(const key_t& key) {
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
throw std::range_error("There is no such key in cache");
} else {
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
return it->second->second;
}
}
void remove(key_t const& key) {
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
throw std::range_error("There is no such key in cache");
} else {
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
}
void clear() {
_cache_items_map.clear();
_cache_items_list.clear();
}
bool exists(const key_t& key) const {
return _cache_items_map.find(key) != _cache_items_map.end();
}
size_t size() const {
return _cache_items_map.size();
}
private:
std::list<key_value_pair_t> _cache_items_list;
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
};
}
}
#endif

View File

@ -58,6 +58,7 @@ SimpleHttpClient::SimpleHttpClient(GeneralClientConnection* connection,
_locationRewriter({nullptr, nullptr}),
_nextChunkedSize(0),
_result(nullptr),
_jwt(""),
_maxPacketSize(MaxPacketSize),
_maxRetries(3),
_retryWaitTime(1 * 1000 * 1000),
@ -443,12 +444,16 @@ void SimpleHttpClient::clearReadBuffer() {
/// @brief sets username and password
////////////////////////////////////////////////////////////////////////////////
void SimpleHttpClient::setJwt(std::string const& jwt) {
_jwt = jwt;
}
void SimpleHttpClient::setUserNamePassword(std::string const& prefix,
std::string const& username,
std::string const& password) {
std::string value =
arangodb::basics::StringUtils::encodeBase64(username + ":" + password);
_pathToBasicAuth.push_back(std::make_pair(prefix, value));
}
@ -572,6 +577,11 @@ void SimpleHttpClient::setRequest(
_writeBuffer.appendText(TRI_CHAR_LENGTH_PAIR("\r\n"));
}
}
if (!_jwt.empty()) {
_writeBuffer.appendText(TRI_CHAR_LENGTH_PAIR("Authorization: bearer "));
_writeBuffer.appendText(_jwt);
_writeBuffer.appendText(TRI_CHAR_LENGTH_PAIR("\r\n"));
}
for (auto const& header : headers) {
_writeBuffer.appendText(header.first);

View File

@ -163,6 +163,8 @@ class SimpleHttpClient {
/// @param password password
//////////////////////////////////////////////////////////////////////////////
void setJwt(std::string const& jwt);
void setUserNamePassword(std::string const& prefix,
std::string const& username,
std::string const& password);
@ -415,6 +417,7 @@ class SimpleHttpClient {
SimpleHttpResult* _result;
std::vector<std::pair<std::string, std::string>> _pathToBasicAuth;
std::string _jwt;
size_t _maxPacketSize;

View File

@ -539,7 +539,7 @@ static std::string GetEndpointFromUrl(std::string const& url) {
/// @LIT{body} attribute of the result object.
////////////////////////////////////////////////////////////////////////////////
static void JS_Download(v8::FunctionCallbackInfo<v8::Value> const& args) {
void JS_Download(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);

View File

@ -191,4 +191,6 @@ void TRI_InitV8Utils(v8::Isolate* isolate, v8::Handle<v8::Context>,
std::string const& startupPath,
std::string const& modules);
void JS_Download(v8::FunctionCallbackInfo<v8::Value> const& args);
#endif

View File

@ -89,7 +89,7 @@ ${ARANGOD} \
--server.authentication false \
--server.endpoint tcp://127.0.0.1:$(( $BASE + $aid )) \
--server.statistics false \
--server.threads $NATH \
--server.threads 4 \
--log.force-direct true \
> /tmp/cluster/$(( $BASE + $aid )).stdout 2>&1 &

View File

@ -9,6 +9,7 @@ function help() {
echo " -d/--ndbservers # db servers (odd integer default: 2))"
echo " -s/--secondaries Start secondaries (0|1 default: 0)"
echo " -t/--transport Protocol (ssl|tcp default: tcp)"
echo " -j/--jwt-secret JWT-Secret (string default: )"
echo " --log-level-a Log level (agency) (INFO|DEBUG|TRACE default: INFO)"
echo " --log-level-c Log level (cluster) (INFO|DEBUG|TRACE default: INFO)"
echo " -i/--interactive Interactive mode (C|D|R default: '')"
@ -34,6 +35,7 @@ XTERM="x-terminal-emulator"
XTERMOPTIONS="--geometry=80x43"
SECONDARIES=0
BUILD="build"
JWT_SECRET=""
while [[ ${1} ]]; do
case "${1}" in
@ -69,6 +71,10 @@ while [[ ${1} ]]; do
INTERACTIVE_MODE=${2}
shift
;;
-j|--jwt-secret)
JWT_SECRET=${2}
shift
;;
-x|--xterm)
XTERM=${2}
shift
@ -103,14 +109,6 @@ if [ "$POOLSZ" == "" ] ; then
fi
if [ "$TRANSPORT" == "ssl" ]; then
SSLKEYFILE="--ssl.keyfile UnitTests/server.pem"
CURL="curl --insecure -s -f -X GET https:"
else
SSLKEYFILE=""
CURL="curl -s -f -X GET http:"
fi
printf "Starting agency ... \n"
printf " # agents: %s," "$NRAGENTS"
printf " # db servers: %s," "$NRDBSERVERS"
@ -158,6 +156,22 @@ if [ -d cluster-init ];then
fi
mkdir -p cluster
if [ -z "$JWT_SECRET" ];then
AUTHENTICATION="--server.authentication false"
AUTHORIZATION_HEADER=""
else
AUTHENTICATION="--server.jwt-secret $JWT_SECRET"
AUTHORIZATION_HEADER="Authorization: bearer $(jwtgen -a HS256 -s $JWT_SECRET -c 'iss=arangodb' -c 'preferred_username=root')"
fi
if [ "$TRANSPORT" == "ssl" ]; then
SSLKEYFILE="--ssl.keyfile UnitTests/server.pem"
CURL="curl --insecure $CURL_AUTHENTICATION -s -f -X GET https:"
else
SSLKEYFILE=""
CURL="curl -s -f $CURL_AUTHENTICATION -X GET http:"
fi
echo Starting agency ...
for aid in `seq 0 $(( $NRAGENTS - 1 ))`; do
port=$(( $BASE + $aid ))
@ -180,12 +194,12 @@ for aid in `seq 0 $(( $NRAGENTS - 1 ))`; do
--javascript.startup-directory ./js \
--javascript.module-directory ./enterprise/js \
--javascript.v8-contexts 1 \
--server.authentication false \
--server.endpoint $TRANSPORT://0.0.0.0:$port \
--server.statistics false \
--server.threads 16 \
--log.file cluster/$port.log \
--log.force-direct true \
$AUTHENTICATION \
$SSLKEYFILE \
> cluster/$port.stdout 2>&1 &
done
@ -213,11 +227,11 @@ start() {
--log.level info \
--server.statistics true \
--server.threads 5 \
--server.authentication false \
--javascript.startup-directory ./js \
--javascript.module-directory ./enterprise/js \
--javascript.app-path cluster/apps$PORT \
--log.force-direct true \
$AUTHENTICATION \
$SSLKEYFILE \
> cluster/$PORT.stdout 2>&1 &
}
@ -247,7 +261,7 @@ startTerminal() {
--javascript.startup-directory ./js \
--javascript.module-directory ./enterprise/js \
--javascript.app-path ./js/apps \
--server.authentication false \
$AUTHENTICATION \
$SSLKEYFILE \
--console &
}
@ -278,7 +292,7 @@ startDebugger() {
--javascript.module-directory ./enterprise/js \
--javascript.app-path ./js/apps \
$SSLKEYFILE \
--server.authentication false &
$AUTHENTICATION &
$XTERM $XTERMOPTIONS -e gdb ${BUILD}/bin/arangod -p $! &
}
@ -307,7 +321,7 @@ startRR() {
--javascript.startup-directory ./js \
--javascript.module-directory ./enterprise/js \
--javascript.app-path ./js/apps \
--server.authentication false \
$AUTHENTICATION \
$SSLKEYFILE \
--console &
}
@ -345,7 +359,11 @@ echo Waiting for cluster to come up...
testServer() {
PORT=$1
while true ; do
${CURL}//127.0.0.1:$PORT/_api/version > /dev/null 2>&1
if [ -z "$AUTHORIZATION_HEADER" ]; then
${CURL}//127.0.0.1:$PORT/_api/version > /dev/null 2>&1
else
${CURL}//127.0.0.1:$PORT/_api/version -H "$AUTHORIZATION_HEADER" > /dev/null 2>&1
fi
if [ "$?" != "0" ] ; then
echo Server on port $PORT does not answer yet.
else
@ -385,7 +403,7 @@ if [ "$SECONDARIES" == "1" ] ; then
--server.statistics true \
--javascript.startup-directory ./js \
--javascript.module-directory ./enterprise/js \
--server.authentication false \
$AUTHENTICATION \
$SSLKEYFILE \
--javascript.app-path ./js/apps \
> cluster/$PORT.stdout 2>&1 &