2022-02-11 18:29:41 +08:00
|
|
|
// Copyright 2022 Memgraph Ltd.
|
2021-10-03 18:07:04 +08:00
|
|
|
//
|
|
|
|
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
|
|
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
|
|
|
// this file except in compliance with the License. You may obtain a copy of the License at https://memgraph.com/legal.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
2018-07-27 16:54:20 +08:00
|
|
|
#include "auth/models.hpp"
|
|
|
|
|
2018-08-14 17:34:00 +08:00
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
#include <gflags/gflags.h>
|
|
|
|
|
2018-07-27 16:54:20 +08:00
|
|
|
#include "auth/crypto.hpp"
|
2018-08-14 17:34:00 +08:00
|
|
|
#include "auth/exceptions.hpp"
|
2018-07-27 16:54:20 +08:00
|
|
|
#include "utils/cast.hpp"
|
2021-09-30 01:14:39 +08:00
|
|
|
#include "utils/license.hpp"
|
|
|
|
#include "utils/settings.hpp"
|
2019-02-22 20:20:54 +08:00
|
|
|
#include "utils/string.hpp"
|
2018-08-14 17:34:00 +08:00
|
|
|
|
2021-09-30 01:14:39 +08:00
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
|
2018-08-14 17:34:00 +08:00
|
|
|
|
2022-03-31 19:52:43 +08:00
|
|
|
inline constexpr std::string_view default_password_regex = ".+";
|
2021-09-30 01:14:39 +08:00
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
|
2018-08-14 17:34:00 +08:00
|
|
|
"The regular expression that should be used to match the entire "
|
|
|
|
"entered password to ensure its strength.");
|
2018-07-27 16:54:20 +08:00
|
|
|
|
2022-03-09 22:53:33 +08:00
|
|
|
namespace memgraph::auth {
|
2022-02-17 17:51:04 +08:00
|
|
|
namespace {
|
2022-02-17 17:35:48 +08:00
|
|
|
// Constant list of all available permissions.
|
2022-02-17 17:51:04 +08:00
|
|
|
const std::vector<Permission> kPermissionsAll = {
|
2022-02-17 17:35:48 +08:00
|
|
|
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
|
|
|
|
Permission::SET, Permission::REMOVE, Permission::INDEX, Permission::STATS,
|
|
|
|
Permission::CONSTRAINT, Permission::DUMP, Permission::AUTH, Permission::REPLICATION,
|
|
|
|
Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
|
2022-02-17 17:51:04 +08:00
|
|
|
Permission::CONFIG, Permission::STREAM, Permission::MODULE_READ, Permission::MODULE_WRITE,
|
2022-07-11 15:20:15 +08:00
|
|
|
Permission::WEBSOCKET, Permission::SCHEMA};
|
2022-02-17 17:51:04 +08:00
|
|
|
} // namespace
|
2022-02-17 17:35:48 +08:00
|
|
|
|
2018-08-14 17:34:00 +08:00
|
|
|
std::string PermissionToString(Permission permission) {
|
|
|
|
switch (permission) {
|
|
|
|
case Permission::MATCH:
|
|
|
|
return "MATCH";
|
|
|
|
case Permission::CREATE:
|
|
|
|
return "CREATE";
|
|
|
|
case Permission::MERGE:
|
|
|
|
return "MERGE";
|
|
|
|
case Permission::DELETE:
|
|
|
|
return "DELETE";
|
|
|
|
case Permission::SET:
|
|
|
|
return "SET";
|
|
|
|
case Permission::REMOVE:
|
|
|
|
return "REMOVE";
|
|
|
|
case Permission::INDEX:
|
|
|
|
return "INDEX";
|
2019-02-19 20:31:46 +08:00
|
|
|
case Permission::STATS:
|
|
|
|
return "STATS";
|
2019-05-28 17:08:49 +08:00
|
|
|
case Permission::CONSTRAINT:
|
|
|
|
return "CONSTRAINT";
|
|
|
|
case Permission::DUMP:
|
|
|
|
return "DUMP";
|
2020-10-01 19:58:25 +08:00
|
|
|
case Permission::REPLICATION:
|
|
|
|
return "REPLICATION";
|
2021-06-30 18:31:30 +08:00
|
|
|
case Permission::DURABILITY:
|
|
|
|
return "DURABILITY";
|
2021-03-19 00:24:25 +08:00
|
|
|
case Permission::READ_FILE:
|
|
|
|
return "READ_FILE";
|
2021-03-04 19:20:11 +08:00
|
|
|
case Permission::FREE_MEMORY:
|
|
|
|
return "FREE_MEMORY";
|
2021-05-14 21:38:59 +08:00
|
|
|
case Permission::TRIGGER:
|
|
|
|
return "TRIGGER";
|
2021-06-14 21:47:57 +08:00
|
|
|
case Permission::CONFIG:
|
|
|
|
return "CONFIG";
|
2018-08-14 17:34:00 +08:00
|
|
|
case Permission::AUTH:
|
|
|
|
return "AUTH";
|
2021-06-28 23:21:13 +08:00
|
|
|
case Permission::STREAM:
|
|
|
|
return "STREAM";
|
2022-02-11 18:29:41 +08:00
|
|
|
case Permission::MODULE_READ:
|
|
|
|
return "MODULE_READ";
|
|
|
|
case Permission::MODULE_WRITE:
|
|
|
|
return "MODULE_WRITE";
|
2022-02-17 17:35:48 +08:00
|
|
|
case Permission::WEBSOCKET:
|
|
|
|
return "WEBSOCKET";
|
2022-07-11 15:20:15 +08:00
|
|
|
case Permission::SCHEMA:
|
|
|
|
return "SCHEMA";
|
2018-08-14 17:34:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-22 16:59:46 +08:00
|
|
|
std::string PermissionLevelToString(PermissionLevel level) {
|
|
|
|
switch (level) {
|
|
|
|
case PermissionLevel::GRANT:
|
|
|
|
return "GRANT";
|
|
|
|
case PermissionLevel::NEUTRAL:
|
|
|
|
return "NEUTRAL";
|
|
|
|
case PermissionLevel::DENY:
|
|
|
|
return "DENY";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-27 16:54:20 +08:00
|
|
|
Permissions::Permissions(uint64_t grants, uint64_t denies) {
|
|
|
|
// The deny bitmask has higher priority than the grant bitmask.
|
|
|
|
denies_ = denies;
|
|
|
|
// Mask out the grant bitmask to make sure that it is correct.
|
|
|
|
grants_ = grants & (~denies);
|
|
|
|
}
|
|
|
|
|
|
|
|
PermissionLevel Permissions::Has(Permission permission) const {
|
|
|
|
// Check for the deny first because it has greater priority than a grant.
|
|
|
|
if (denies_ & utils::UnderlyingCast(permission)) {
|
2018-08-14 17:34:00 +08:00
|
|
|
return PermissionLevel::DENY;
|
2021-09-30 01:14:39 +08:00
|
|
|
}
|
|
|
|
if (grants_ & utils::UnderlyingCast(permission)) {
|
2018-08-14 17:34:00 +08:00
|
|
|
return PermissionLevel::GRANT;
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
2018-08-14 17:34:00 +08:00
|
|
|
return PermissionLevel::NEUTRAL;
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Permissions::Grant(Permission permission) {
|
|
|
|
// Remove the possible deny.
|
|
|
|
denies_ &= ~utils::UnderlyingCast(permission);
|
|
|
|
// Now we grant the permission.
|
|
|
|
grants_ |= utils::UnderlyingCast(permission);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Permissions::Revoke(Permission permission) {
|
|
|
|
// Remove the possible grant.
|
|
|
|
grants_ &= ~utils::UnderlyingCast(permission);
|
|
|
|
// Remove the possible deny.
|
|
|
|
denies_ &= ~utils::UnderlyingCast(permission);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Permissions::Deny(Permission permission) {
|
|
|
|
// First deny the permission.
|
|
|
|
denies_ |= utils::UnderlyingCast(permission);
|
|
|
|
// Remove the possible grant.
|
|
|
|
grants_ &= ~utils::UnderlyingCast(permission);
|
|
|
|
}
|
|
|
|
|
2018-08-14 17:34:00 +08:00
|
|
|
std::vector<Permission> Permissions::GetGrants() const {
|
|
|
|
std::vector<Permission> ret;
|
|
|
|
for (const auto &permission : kPermissionsAll) {
|
|
|
|
if (Has(permission) == PermissionLevel::GRANT) {
|
|
|
|
ret.push_back(permission);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Permission> Permissions::GetDenies() const {
|
|
|
|
std::vector<Permission> ret;
|
|
|
|
for (const auto &permission : kPermissionsAll) {
|
|
|
|
if (Has(permission) == PermissionLevel::DENY) {
|
|
|
|
ret.push_back(permission);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-27 16:54:20 +08:00
|
|
|
nlohmann::json Permissions::Serialize() const {
|
|
|
|
nlohmann::json data = nlohmann::json::object();
|
|
|
|
data["grants"] = grants_;
|
|
|
|
data["denies"] = denies_;
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
|
|
|
if (!data.is_object()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load permissions data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
2021-02-18 22:32:43 +08:00
|
|
|
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load permissions data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
return {data["grants"], data["denies"]};
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Permissions::grants() const { return grants_; }
|
|
|
|
uint64_t Permissions::denies() const { return denies_; }
|
|
|
|
|
|
|
|
bool operator==(const Permissions &first, const Permissions &second) {
|
|
|
|
return first.grants() == second.grants() && first.denies() == second.denies();
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
|
2018-07-27 16:54:20 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
|
2018-07-27 16:54:20 +08:00
|
|
|
|
|
|
|
Role::Role(const std::string &rolename, const Permissions &permissions)
|
2019-02-22 20:20:54 +08:00
|
|
|
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
|
2018-07-27 16:54:20 +08:00
|
|
|
|
|
|
|
const std::string &Role::rolename() const { return rolename_; }
|
|
|
|
const Permissions &Role::permissions() const { return permissions_; }
|
|
|
|
Permissions &Role::permissions() { return permissions_; }
|
|
|
|
|
|
|
|
nlohmann::json Role::Serialize() const {
|
|
|
|
nlohmann::json data = nlohmann::json::object();
|
|
|
|
data["rolename"] = rolename_;
|
|
|
|
data["permissions"] = permissions_.Serialize();
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
Role Role::Deserialize(const nlohmann::json &data) {
|
|
|
|
if (!data.is_object()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load role data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load role data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
auto permissions = Permissions::Deserialize(data["permissions"]);
|
|
|
|
return {data["rolename"], permissions};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const Role &first, const Role &second) {
|
2021-02-18 22:32:43 +08:00
|
|
|
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
2018-07-27 16:54:20 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
|
|
|
|
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
|
2018-07-27 16:54:20 +08:00
|
|
|
|
|
|
|
bool User::CheckPassword(const std::string &password) {
|
2021-09-30 01:14:39 +08:00
|
|
|
if (password_hash_.empty()) return true;
|
2018-07-27 16:54:20 +08:00
|
|
|
return VerifyPassword(password, password_hash_);
|
|
|
|
}
|
|
|
|
|
2019-04-23 17:00:49 +08:00
|
|
|
void User::UpdatePassword(const std::optional<std::string> &password) {
|
2021-09-30 01:14:39 +08:00
|
|
|
if (!password) {
|
2018-08-14 17:34:00 +08:00
|
|
|
if (!FLAGS_auth_password_permit_null) {
|
|
|
|
throw AuthException("Null passwords aren't permitted!");
|
|
|
|
}
|
|
|
|
password_hash_ = "";
|
2021-09-30 01:14:39 +08:00
|
|
|
return;
|
2018-08-14 17:34:00 +08:00
|
|
|
}
|
2021-09-30 01:14:39 +08:00
|
|
|
|
|
|
|
if (FLAGS_auth_password_strength_regex != default_password_regex) {
|
|
|
|
if (const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
|
|
|
license_check_result.HasError()) {
|
|
|
|
throw AuthException(
|
|
|
|
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
|
|
|
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
|
|
|
default_password_regex,
|
|
|
|
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::regex re(FLAGS_auth_password_strength_regex);
|
|
|
|
if (!std::regex_match(*password, re)) {
|
|
|
|
throw AuthException(
|
|
|
|
"The user password doesn't conform to the required strength! Regex: "
|
|
|
|
"\"{}\"",
|
|
|
|
FLAGS_auth_password_strength_regex);
|
|
|
|
}
|
|
|
|
|
|
|
|
password_hash_ = EncryptPassword(*password);
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void User::SetRole(const Role &role) { role_.emplace(role); }
|
|
|
|
|
2019-04-23 17:00:49 +08:00
|
|
|
void User::ClearRole() { role_ = std::nullopt; }
|
2018-08-14 17:34:00 +08:00
|
|
|
|
2021-07-22 22:22:08 +08:00
|
|
|
Permissions User::GetPermissions() const {
|
2018-07-27 16:54:20 +08:00
|
|
|
if (role_) {
|
|
|
|
return Permissions(permissions_.grants() | role_->permissions().grants(),
|
|
|
|
permissions_.denies() | role_->permissions().denies());
|
|
|
|
}
|
|
|
|
return permissions_;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string &User::username() const { return username_; }
|
|
|
|
|
2018-08-22 16:59:46 +08:00
|
|
|
const Permissions &User::permissions() const { return permissions_; }
|
2018-07-27 16:54:20 +08:00
|
|
|
Permissions &User::permissions() { return permissions_; }
|
|
|
|
|
2021-07-22 22:22:08 +08:00
|
|
|
const Role *User::role() const {
|
|
|
|
if (role_.has_value()) {
|
|
|
|
return &role_.value();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2018-07-27 16:54:20 +08:00
|
|
|
|
|
|
|
nlohmann::json User::Serialize() const {
|
|
|
|
nlohmann::json data = nlohmann::json::object();
|
|
|
|
data["username"] = username_;
|
|
|
|
data["password_hash"] = password_hash_;
|
|
|
|
data["permissions"] = permissions_.Serialize();
|
|
|
|
// The role shouldn't be serialized here, it is stored as a foreign key.
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
User User::Deserialize(const nlohmann::json &data) {
|
|
|
|
if (!data.is_object()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load user data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
2021-02-18 22:32:43 +08:00
|
|
|
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
|
2018-08-14 17:34:00 +08:00
|
|
|
throw AuthException("Couldn't load user data!");
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
|
|
|
auto permissions = Permissions::Deserialize(data["permissions"]);
|
|
|
|
return {data["username"], data["password_hash"], permissions};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const User &first, const User &second) {
|
2021-02-18 22:32:43 +08:00
|
|
|
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
|
|
|
|
first.permissions_ == second.permissions_ && first.role_ == second.role_;
|
2018-07-27 16:54:20 +08:00
|
|
|
}
|
2022-03-09 22:53:33 +08:00
|
|
|
} // namespace memgraph::auth
|