2018-04-20 20:58:49 +08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <chrono>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <exception>
|
|
|
|
#include <functional>
|
|
|
|
#include <limits>
|
|
|
|
#include <thread>
|
2016-08-10 16:39:02 +08:00
|
|
|
|
2017-07-06 19:53:39 +08:00
|
|
|
#include <gflags/gflags.h>
|
|
|
|
#include <glog/logging.h>
|
2017-05-22 18:31:04 +08:00
|
|
|
|
2018-08-31 16:32:53 +08:00
|
|
|
#include "communication/server.hpp"
|
2018-08-22 21:00:16 +08:00
|
|
|
#include "memgraph_init.hpp"
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
#include "query/exceptions.hpp"
|
2020-01-15 20:58:41 +08:00
|
|
|
#include "query/procedure/module.hpp"
|
2020-02-05 22:05:18 +08:00
|
|
|
#include "storage/v2/storage.hpp"
|
2018-06-20 19:46:54 +08:00
|
|
|
#include "telemetry/telemetry.hpp"
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
#include "utils/file.hpp"
|
2017-06-09 21:48:40 +08:00
|
|
|
#include "utils/flag_validation.hpp"
|
2020-01-15 20:58:41 +08:00
|
|
|
#include "utils/string.hpp"
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
#include "glue/auth.hpp"
|
|
|
|
#endif
|
|
|
|
|
2017-12-19 19:40:30 +08:00
|
|
|
// General purpose flags.
|
2019-12-08 18:42:59 +08:00
|
|
|
DEFINE_string(bolt_address, "0.0.0.0",
|
|
|
|
"IP address on which the Bolt server should listen.");
|
|
|
|
DEFINE_VALIDATED_int32(bolt_port, 7687,
|
|
|
|
"Port on which the Bolt server should listen.",
|
2018-01-15 21:03:07 +08:00
|
|
|
FLAG_IN_RANGE(0, std::numeric_limits<uint16_t>::max()));
|
2019-12-08 18:42:59 +08:00
|
|
|
DEFINE_VALIDATED_int32(
|
|
|
|
bolt_num_workers, std::max(std::thread::hardware_concurrency(), 1U),
|
|
|
|
"Number of workers used by the Bolt server. By default, this will be the "
|
|
|
|
"number of processing units available on the machine.",
|
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
|
|
|
DEFINE_VALIDATED_int32(
|
|
|
|
bolt_session_inactivity_timeout, 1800,
|
|
|
|
"Time in seconds after which inactive Bolt sessions will be "
|
|
|
|
"closed.",
|
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
|
|
|
DEFINE_string(bolt_cert_file, "",
|
|
|
|
"Certificate file which should be used for the Bolt server.");
|
|
|
|
DEFINE_string(bolt_key_file, "",
|
|
|
|
"Key file which should be used for the Bolt server.");
|
2018-08-22 21:00:16 +08:00
|
|
|
|
2019-10-09 22:00:02 +08:00
|
|
|
// General purpose flags.
|
|
|
|
DEFINE_string(data_directory, "mg_data",
|
|
|
|
"Path to directory in which to save all permanent data.");
|
|
|
|
|
|
|
|
// Storage flags.
|
|
|
|
DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30,
|
|
|
|
"Storage garbage collector interval (in seconds).",
|
|
|
|
FLAG_IN_RANGE(1, 24 * 3600));
|
|
|
|
DEFINE_bool(storage_properties_on_edges, false,
|
|
|
|
"Controls whether edges have properties.");
|
|
|
|
DEFINE_bool(storage_recover_on_startup, false,
|
|
|
|
"Controls whether the storage recovers persisted data on startup.");
|
|
|
|
DEFINE_VALIDATED_uint64(storage_snapshot_interval_sec, 0,
|
|
|
|
"Storage snapshot creation interval (in seconds). Set "
|
|
|
|
"to 0 to disable periodic snapshot creation.",
|
|
|
|
FLAG_IN_RANGE(0, 7 * 24 * 3600));
|
|
|
|
DEFINE_bool(storage_wal_enabled, false,
|
|
|
|
"Controls whether the storage uses write-ahead-logging. To enable "
|
|
|
|
"WAL periodic snapshots must be enabled.");
|
|
|
|
DEFINE_VALIDATED_uint64(storage_snapshot_retention_count, 3,
|
|
|
|
"The number of snapshots that should always be kept.",
|
|
|
|
FLAG_IN_RANGE(1, 1000000));
|
|
|
|
DEFINE_VALIDATED_uint64(storage_wal_file_size_kib,
|
|
|
|
storage::Config::Durability().wal_file_size_kibibytes,
|
|
|
|
"Minimum file size of each WAL file.",
|
|
|
|
FLAG_IN_RANGE(1, 1000 * 1024));
|
|
|
|
DEFINE_VALIDATED_uint64(
|
|
|
|
storage_wal_file_flush_every_n_tx,
|
|
|
|
storage::Config::Durability().wal_file_flush_every_n_tx,
|
|
|
|
"Issue a 'fsync' call after this amount of transactions are written to the "
|
|
|
|
"WAL file. Set to 1 for fully synchronous operation.",
|
|
|
|
FLAG_IN_RANGE(1, 1000000));
|
|
|
|
DEFINE_bool(storage_snapshot_on_exit, false,
|
|
|
|
"Controls whether the storage creates another snapshot on exit.");
|
|
|
|
|
2018-06-20 19:46:54 +08:00
|
|
|
DEFINE_bool(telemetry_enabled, false,
|
|
|
|
"Set to true to enable telemetry. We collect information about the "
|
|
|
|
"running system (CPU and memory information) and information about "
|
|
|
|
"the database runtime (vertex and edge counts and resource usage) "
|
|
|
|
"to allow for easier improvement of the product.");
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
|
2019-02-19 20:50:46 +08:00
|
|
|
// Audit logging flags.
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2019-02-19 20:50:46 +08:00
|
|
|
DEFINE_bool(audit_enabled, false, "Set to true to enable audit logging.");
|
|
|
|
DEFINE_VALIDATED_int32(audit_buffer_size, audit::kBufferSizeDefault,
|
|
|
|
"Maximum number of items in the audit log buffer.",
|
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
|
|
|
DEFINE_VALIDATED_int32(
|
|
|
|
audit_buffer_flush_interval_ms, audit::kBufferFlushIntervalMillisDefault,
|
|
|
|
"Interval (in milliseconds) used for flushing the audit log buffer.",
|
|
|
|
FLAG_IN_RANGE(10, INT32_MAX));
|
2020-02-05 22:05:18 +08:00
|
|
|
#endif
|
2019-02-19 20:50:46 +08:00
|
|
|
|
2019-11-25 22:08:16 +08:00
|
|
|
// Query flags.
|
|
|
|
DEFINE_uint64(query_execution_timeout_sec, 180,
|
|
|
|
"Maximum allowed query execution time. Queries exceeding this "
|
|
|
|
"limit will be aborted. Value of 0 means no limit.");
|
|
|
|
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
DEFINE_VALIDATED_string(
|
|
|
|
query_modules_directory, "",
|
2019-12-09 18:31:27 +08:00
|
|
|
"Directory where modules with custom query procedures are stored.", {
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
if (value.empty()) return true;
|
|
|
|
if (utils::DirExists(value)) return true;
|
|
|
|
std::cout << "Expected --" << flagname << " to point to a directory."
|
|
|
|
<< std::endl;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
using ServerT = communication::Server<BoltSession, SessionData>;
|
|
|
|
using communication::ServerContext;
|
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2020-01-15 20:58:41 +08:00
|
|
|
class AuthQueryHandler final : public query::AuthQueryHandler {
|
|
|
|
auth::Auth *auth_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit AuthQueryHandler(auth::Auth *auth) : auth_(auth) {}
|
|
|
|
|
|
|
|
bool CreateUser(const std::string &username,
|
|
|
|
const std::optional<std::string> &password) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
return !!auth_->AddUser(username, password);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DropUser(const std::string &username) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) return false;
|
|
|
|
return auth_->RemoveUser(username);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetPassword(const std::string &username,
|
|
|
|
const std::optional<std::string> &password) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist.",
|
|
|
|
username);
|
|
|
|
}
|
|
|
|
user->UpdatePassword(password);
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CreateRole(const std::string &rolename) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
return !!auth_->AddRole(rolename);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DropRole(const std::string &rolename) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) return false;
|
|
|
|
return auth_->RemoveRole(rolename);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetUsernames() override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<query::TypedValue> usernames;
|
|
|
|
const auto &users = auth_->AllUsers();
|
|
|
|
usernames.reserve(users.size());
|
|
|
|
for (const auto &user : users) {
|
|
|
|
usernames.emplace_back(user.username());
|
|
|
|
}
|
|
|
|
return usernames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetRolenames() override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<query::TypedValue> rolenames;
|
|
|
|
const auto &roles = auth_->AllRoles();
|
|
|
|
rolenames.reserve(roles.size());
|
|
|
|
for (const auto &role : roles) {
|
|
|
|
rolenames.emplace_back(role.rolename());
|
|
|
|
}
|
|
|
|
return rolenames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> GetRolenameForUser(
|
|
|
|
const std::string &username) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .",
|
|
|
|
username);
|
|
|
|
}
|
|
|
|
if (user->role()) return user->role()->rolename();
|
|
|
|
return std::nullopt;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetUsernamesForRole(
|
|
|
|
const std::string &rolename) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) {
|
|
|
|
throw query::QueryRuntimeException("Role '{}' doesn't exist.",
|
|
|
|
rolename);
|
|
|
|
}
|
|
|
|
std::vector<query::TypedValue> usernames;
|
|
|
|
const auto &users = auth_->AllUsersForRole(rolename);
|
|
|
|
usernames.reserve(users.size());
|
|
|
|
for (const auto &user : users) {
|
|
|
|
usernames.emplace_back(user.username());
|
|
|
|
}
|
|
|
|
return usernames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetRole(const std::string &username,
|
|
|
|
const std::string &rolename) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .",
|
|
|
|
username);
|
|
|
|
}
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) {
|
|
|
|
throw query::QueryRuntimeException("Role '{}' doesn't exist .",
|
|
|
|
rolename);
|
|
|
|
}
|
|
|
|
if (user->role()) {
|
|
|
|
throw query::QueryRuntimeException(
|
|
|
|
"User '{}' is already a member of role '{}'.", username,
|
|
|
|
user->role()->rolename());
|
|
|
|
}
|
|
|
|
user->SetRole(*role);
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearRole(const std::string &username) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .",
|
|
|
|
username);
|
|
|
|
}
|
|
|
|
user->ClearRole();
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::vector<query::TypedValue>> GetPrivileges(
|
|
|
|
const std::string &user_or_role) override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<std::vector<query::TypedValue>> grants;
|
|
|
|
auto user = auth_->GetUser(user_or_role);
|
|
|
|
auto role = auth_->GetRole(user_or_role);
|
|
|
|
if (!user && !role) {
|
|
|
|
throw query::QueryRuntimeException("User or role '{}' doesn't exist.",
|
|
|
|
user_or_role);
|
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
const auto &permissions = user->GetPermissions();
|
|
|
|
for (const auto &privilege : query::kPrivilegesAll) {
|
|
|
|
auto permission = glue::PrivilegeToPermission(privilege);
|
|
|
|
auto effective = permissions.Has(permission);
|
|
|
|
if (permissions.Has(permission) != auth::PermissionLevel::NEUTRAL) {
|
|
|
|
std::vector<std::string> description;
|
|
|
|
auto user_level = user->permissions().Has(permission);
|
|
|
|
if (user_level == auth::PermissionLevel::GRANT) {
|
|
|
|
description.emplace_back("GRANTED TO USER");
|
|
|
|
} else if (user_level == auth::PermissionLevel::DENY) {
|
|
|
|
description.emplace_back("DENIED TO USER");
|
|
|
|
}
|
|
|
|
if (user->role()) {
|
|
|
|
auto role_level = user->role()->permissions().Has(permission);
|
|
|
|
if (role_level == auth::PermissionLevel::GRANT) {
|
|
|
|
description.emplace_back("GRANTED TO ROLE");
|
|
|
|
} else if (role_level == auth::PermissionLevel::DENY) {
|
|
|
|
description.emplace_back("DENIED TO ROLE");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
grants.push_back(
|
|
|
|
{query::TypedValue(auth::PermissionToString(permission)),
|
|
|
|
query::TypedValue(auth::PermissionLevelToString(effective)),
|
|
|
|
query::TypedValue(utils::Join(description, ", "))});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const auto &permissions = role->permissions();
|
|
|
|
for (const auto &privilege : query::kPrivilegesAll) {
|
|
|
|
auto permission = glue::PrivilegeToPermission(privilege);
|
|
|
|
auto effective = permissions.Has(permission);
|
|
|
|
if (effective != auth::PermissionLevel::NEUTRAL) {
|
|
|
|
std::string description;
|
|
|
|
if (effective == auth::PermissionLevel::GRANT) {
|
|
|
|
description = "GRANTED TO ROLE";
|
|
|
|
} else if (effective == auth::PermissionLevel::DENY) {
|
|
|
|
description = "DENIED TO ROLE";
|
|
|
|
}
|
|
|
|
grants.push_back(
|
|
|
|
{query::TypedValue(auth::PermissionToString(permission)),
|
|
|
|
query::TypedValue(auth::PermissionLevelToString(effective)),
|
|
|
|
query::TypedValue(description)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return grants;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GrantPrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges,
|
|
|
|
[](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Grant(permission);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void DenyPrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges,
|
|
|
|
[](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Deny(permission);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void RevokePrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges,
|
|
|
|
[](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Revoke(permission);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
template <class TEditFun>
|
|
|
|
void EditPermissions(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges,
|
|
|
|
const TEditFun &edit_fun) {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<auth::Permission> permissions;
|
|
|
|
permissions.reserve(privileges.size());
|
|
|
|
for (const auto &privilege : privileges) {
|
|
|
|
permissions.push_back(glue::PrivilegeToPermission(privilege));
|
|
|
|
}
|
|
|
|
auto user = auth_->GetUser(user_or_role);
|
|
|
|
auto role = auth_->GetRole(user_or_role);
|
|
|
|
if (!user && !role) {
|
|
|
|
throw query::QueryRuntimeException("User or role '{}' doesn't exist.",
|
|
|
|
user_or_role);
|
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
for (const auto &permission : permissions) {
|
|
|
|
edit_fun(&user->permissions(), permission);
|
|
|
|
}
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} else {
|
|
|
|
for (const auto &permission : permissions) {
|
|
|
|
edit_fun(&role->permissions(), permission);
|
|
|
|
}
|
|
|
|
auth_->SaveRole(*role);
|
|
|
|
}
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
class NoAuthInCommunity : public query::QueryRuntimeException {
|
|
|
|
public:
|
|
|
|
NoAuthInCommunity()
|
|
|
|
: query::QueryRuntimeException::QueryRuntimeException(
|
|
|
|
"Auth is not supported in Memgraph Community!") {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class AuthQueryHandler final : public query::AuthQueryHandler {
|
|
|
|
public:
|
|
|
|
bool CreateUser(const std::string &,
|
|
|
|
const std::optional<std::string> &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DropUser(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
|
|
|
void SetPassword(const std::string &,
|
|
|
|
const std::optional<std::string> &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CreateRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
|
|
|
bool DropRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetUsernames() override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetRolenames() override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> GetRolenameForUser(const std::string &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetUsernamesForRole(
|
|
|
|
const std::string &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetRole(const std::string &, const std::string &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
|
|
|
std::vector<std::vector<query::TypedValue>> GetPrivileges(
|
|
|
|
const std::string &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GrantPrivilege(
|
|
|
|
const std::string &,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DenyPrivilege(
|
|
|
|
const std::string &,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RevokePrivilege(
|
|
|
|
const std::string &,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &) override {
|
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|
2020-01-15 20:58:41 +08:00
|
|
|
|
2018-04-20 20:58:49 +08:00
|
|
|
void SingleNodeMain() {
|
2020-01-17 20:02:50 +08:00
|
|
|
std::cout << "You are running Memgraph v" << gflags::VersionString()
|
|
|
|
<< std::endl;
|
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
auto data_directory = std::filesystem::path(FLAGS_data_directory);
|
|
|
|
|
|
|
|
#ifdef MG_ENTERPRISE
|
2019-02-19 20:50:46 +08:00
|
|
|
// All enterprise features should be constructed before the main database
|
|
|
|
// storage. This will cause them to be destructed *after* the main database
|
|
|
|
// storage. That way any errors that happen during enterprise features
|
|
|
|
// destruction won't have an impact on the storage engine.
|
|
|
|
// Example: When the main storage is destructed it makes a snapshot. When
|
|
|
|
// audit logging is destructed it syncs all pending data to disk and that can
|
|
|
|
// fail. That is why it must be destructed *after* the main database storage
|
|
|
|
// to minimise the impact of their failure on the main storage.
|
|
|
|
|
|
|
|
// Begin enterprise features initialization
|
|
|
|
|
|
|
|
// Auth
|
2019-10-09 22:00:02 +08:00
|
|
|
auth::Auth auth{data_directory / "auth"};
|
2019-02-19 20:50:46 +08:00
|
|
|
|
|
|
|
// Audit log
|
2019-10-09 22:00:02 +08:00
|
|
|
audit::Log audit_log{data_directory / "audit", FLAGS_audit_buffer_size,
|
2019-02-19 20:50:46 +08:00
|
|
|
FLAGS_audit_buffer_flush_interval_ms};
|
|
|
|
// Start the log if enabled.
|
|
|
|
if (FLAGS_audit_enabled) {
|
|
|
|
audit_log.Start();
|
|
|
|
}
|
|
|
|
// Setup SIGUSR2 to be used for reopening audit log files, when e.g. logrotate
|
|
|
|
// rotates our audit logs.
|
|
|
|
CHECK(utils::SignalHandler::RegisterHandler(
|
|
|
|
utils::Signal::User2, [&audit_log]() { audit_log.ReopenLog(); }))
|
|
|
|
<< "Unable to register SIGUSR2 handler!";
|
|
|
|
|
|
|
|
// End enterprise features initialization
|
2020-02-05 22:05:18 +08:00
|
|
|
#endif
|
2019-02-19 20:50:46 +08:00
|
|
|
|
|
|
|
// Main storage and execution engines initialization
|
|
|
|
|
2019-10-09 22:00:02 +08:00
|
|
|
storage::Config db_config{
|
|
|
|
.gc = {.type = storage::Config::Gc::Type::PERIODIC,
|
|
|
|
.interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
|
|
|
|
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
|
|
|
|
.durability = {
|
|
|
|
.storage_directory = FLAGS_data_directory,
|
|
|
|
.recover_on_startup = FLAGS_storage_recover_on_startup,
|
|
|
|
.snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
|
|
|
|
.wal_file_size_kibibytes = FLAGS_storage_wal_file_size_kib,
|
|
|
|
.wal_file_flush_every_n_tx = FLAGS_storage_wal_file_flush_every_n_tx,
|
|
|
|
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit}};
|
|
|
|
if (FLAGS_storage_snapshot_interval_sec == 0) {
|
|
|
|
LOG_IF(FATAL, FLAGS_storage_wal_enabled)
|
|
|
|
<< "In order to use write-ahead-logging you must enable "
|
|
|
|
"periodic snapshots by setting the snapshot interval to a "
|
|
|
|
"value larger than 0!";
|
|
|
|
db_config.durability.snapshot_wal_mode =
|
|
|
|
storage::Config::Durability::SnapshotWalMode::DISABLED;
|
|
|
|
} else {
|
|
|
|
if (FLAGS_storage_wal_enabled) {
|
|
|
|
db_config.durability.snapshot_wal_mode = storage::Config::Durability::
|
|
|
|
SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL;
|
|
|
|
} else {
|
|
|
|
db_config.durability.snapshot_wal_mode =
|
|
|
|
storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT;
|
|
|
|
}
|
|
|
|
db_config.durability.snapshot_interval =
|
|
|
|
std::chrono::seconds(FLAGS_storage_snapshot_interval_sec);
|
|
|
|
}
|
|
|
|
storage::Storage db(db_config);
|
2019-10-10 17:23:33 +08:00
|
|
|
query::InterpreterContext interpreter_context{&db};
|
2019-11-25 22:08:16 +08:00
|
|
|
query::SetExecutionTimeout(&interpreter_context,
|
|
|
|
FLAGS_query_execution_timeout_sec);
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2019-10-07 23:31:25 +08:00
|
|
|
SessionData session_data{&db, &interpreter_context, &auth, &audit_log};
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
SessionData session_data{&db, &interpreter_context};
|
|
|
|
#endif
|
2018-06-20 23:44:47 +08:00
|
|
|
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
// Register modules
|
|
|
|
if (!FLAGS_query_modules_directory.empty()) {
|
|
|
|
for (const auto &entry :
|
|
|
|
std::filesystem::directory_iterator(FLAGS_query_modules_directory)) {
|
|
|
|
if (entry.is_regular_file() && entry.path().extension() == ".so")
|
|
|
|
query::procedure::gModuleRegistry.LoadModuleLibrary(entry.path());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Register modules END
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2020-01-15 20:58:41 +08:00
|
|
|
AuthQueryHandler auth_handler(&auth);
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
AuthQueryHandler auth_handler;
|
|
|
|
#endif
|
2020-01-15 20:58:41 +08:00
|
|
|
interpreter_context.auth = &auth_handler;
|
2018-07-06 15:28:05 +08:00
|
|
|
|
2018-06-20 23:44:47 +08:00
|
|
|
ServerContext context;
|
|
|
|
std::string service_name = "Bolt";
|
2019-12-08 18:42:59 +08:00
|
|
|
if (!FLAGS_bolt_key_file.empty() && !FLAGS_bolt_cert_file.empty()) {
|
|
|
|
context = ServerContext(FLAGS_bolt_key_file, FLAGS_bolt_cert_file);
|
2018-06-20 23:44:47 +08:00
|
|
|
service_name = "BoltS";
|
|
|
|
}
|
|
|
|
|
2019-12-08 18:42:59 +08:00
|
|
|
ServerT server({FLAGS_bolt_address, static_cast<uint16_t>(FLAGS_bolt_port)},
|
|
|
|
&session_data, &context, FLAGS_bolt_session_inactivity_timeout,
|
|
|
|
service_name, FLAGS_bolt_num_workers);
|
2017-12-19 19:40:30 +08:00
|
|
|
|
2018-06-20 19:46:54 +08:00
|
|
|
// Setup telemetry
|
2019-04-23 17:00:49 +08:00
|
|
|
std::optional<telemetry::Telemetry> telemetry;
|
2018-06-20 19:46:54 +08:00
|
|
|
if (FLAGS_telemetry_enabled) {
|
|
|
|
telemetry.emplace(
|
|
|
|
"https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/",
|
2019-10-09 22:00:02 +08:00
|
|
|
data_directory / "telemetry", std::chrono::minutes(10));
|
2019-12-11 22:45:34 +08:00
|
|
|
telemetry->AddCollector("db", [&db]() -> nlohmann::json {
|
|
|
|
auto info = db.GetInfo();
|
|
|
|
return {{"vertices", info.vertex_count}, {"edges", info.edge_count}};
|
|
|
|
});
|
|
|
|
}
|
2018-06-20 19:46:54 +08:00
|
|
|
|
2017-12-19 19:40:30 +08:00
|
|
|
// Handler for regular termination signals
|
2019-11-25 22:08:16 +08:00
|
|
|
auto shutdown = [&server, &interpreter_context] {
|
2017-12-19 19:40:30 +08:00
|
|
|
// Server needs to be shutdown first and then the database. This prevents a
|
|
|
|
// race condition when a transaction is accepted during server shutdown.
|
|
|
|
server.Shutdown();
|
2019-11-25 22:08:16 +08:00
|
|
|
// After the server is notified to stop accepting and processing connections
|
|
|
|
// we tell the execution engine to stop processing all pending queries.
|
|
|
|
query::Shutdown(&interpreter_context);
|
2017-12-19 19:40:30 +08:00
|
|
|
};
|
|
|
|
InitSignalHandlers(shutdown);
|
2018-04-20 20:58:49 +08:00
|
|
|
|
2018-10-16 16:58:41 +08:00
|
|
|
CHECK(server.Start()) << "Couldn't start the Bolt server!";
|
2018-01-10 20:56:12 +08:00
|
|
|
server.AwaitShutdown();
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
query::procedure::gModuleRegistry.UnloadAllModules();
|
2017-12-19 19:40:30 +08:00
|
|
|
}
|
|
|
|
|
2019-12-09 18:31:27 +08:00
|
|
|
int main(int argc, char **argv) {
|
|
|
|
google::SetUsageMessage("Memgraph database server");
|
|
|
|
return WithInit(argc, argv, SingleNodeMain);
|
|
|
|
}
|