memgraph/src/auth/models.cpp

308 lines
10 KiB
C++
Raw Normal View History

2022-02-11 18:29:41 +08:00
// Copyright 2022 Memgraph Ltd.
//
// 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.
//
//
#include "auth/models.hpp"
#include <regex>
#include <gflags/gflags.h>
#include "auth/crypto.hpp"
#include "auth/exceptions.hpp"
#include "utils/cast.hpp"
#include "utils/license.hpp"
#include "utils/settings.hpp"
#include "utils/string.hpp"
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
inline constexpr std::string_view default_password_regex = ".+";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
"The regular expression that should be used to match the entire "
"entered password to ensure its strength.");
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,
Create schema DDL expressions * Add initial schema implementation * Add index to schema * List schemas and enable multiple properties * Implement SchemaTypes * Apply suggestions from code review Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> * Address review comments * Remove Map and List * Add schema operations in storage * Add create and show schema queries * Add privileges for schema * Add missing keywords into lexer * Add drop schema query * Add schema visitors * Update metadata * Add PrepareSchemaQuery function * Implement show schemas * Add show schema query * Fix schema visitor * Add common schema type * Fix grammar * Temporary create ddl logic * Fix naming for schemaproperty type to schema type * Rename schemaproperty to schemapropertytype * Enable Create schema ddl * Override visitPropertyType * Add initial schema implementation * Add initial schema implementation * Add index to schema * List schemas and enable multiple properties * Implement SchemaTypes * Apply suggestions from code review Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> * Address review comments * Remove Map and List * Apply suggestions from code review Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com> Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com> * Add verification on creation and deletion * Rename DeleteSchema to DropSchema * Remove list and map from lexer * Fix grammar with schemaTypeMap * Add privilege and cypher visitor tests * Catch repeating type name in schema definition * Fix conflicting keywords * Add notifications * Drop float support * Finish interpreter tests * Fix tests * Fix clang tidy errors * Fix GetSchema * Replace for with transfrom * Add cloning og schema_property_map * Address review comments * Rename SchemaPropertyType to SchemaType * Remove inline * Assert of schema properties Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com>
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
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";
case Permission::STATS:
return "STATS";
case Permission::CONSTRAINT:
return "CONSTRAINT";
case Permission::DUMP:
return "DUMP";
case Permission::REPLICATION:
return "REPLICATION";
2021-06-30 18:31:30 +08:00
case Permission::DURABILITY:
return "DURABILITY";
case Permission::READ_FILE:
return "READ_FILE";
case Permission::FREE_MEMORY:
return "FREE_MEMORY";
case Permission::TRIGGER:
return "TRIGGER";
case Permission::CONFIG:
return "CONFIG";
case Permission::AUTH:
return "AUTH";
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";
Create schema DDL expressions * Add initial schema implementation * Add index to schema * List schemas and enable multiple properties * Implement SchemaTypes * Apply suggestions from code review Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> * Address review comments * Remove Map and List * Add schema operations in storage * Add create and show schema queries * Add privileges for schema * Add missing keywords into lexer * Add drop schema query * Add schema visitors * Update metadata * Add PrepareSchemaQuery function * Implement show schemas * Add show schema query * Fix schema visitor * Add common schema type * Fix grammar * Temporary create ddl logic * Fix naming for schemaproperty type to schema type * Rename schemaproperty to schemapropertytype * Enable Create schema ddl * Override visitPropertyType * Add initial schema implementation * Add initial schema implementation * Add index to schema * List schemas and enable multiple properties * Implement SchemaTypes * Apply suggestions from code review Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> * Address review comments * Remove Map and List * Apply suggestions from code review Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com> Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com> * Add verification on creation and deletion * Rename DeleteSchema to DropSchema * Remove list and map from lexer * Fix grammar with schemaTypeMap * Add privilege and cypher visitor tests * Catch repeating type name in schema definition * Fix conflicting keywords * Add notifications * Drop float support * Finish interpreter tests * Fix tests * Fix clang tidy errors * Fix GetSchema * Replace for with transfrom * Add cloning og schema_property_map * Address review comments * Rename SchemaPropertyType to SchemaType * Remove inline * Assert of schema properties Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com> Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com> Co-authored-by: Kostas Kyrimis <kostaskyrim@gmail.com>
2022-07-11 15:20:15 +08:00
case Permission::SCHEMA:
return "SCHEMA";
}
}
std::string PermissionLevelToString(PermissionLevel level) {
switch (level) {
case PermissionLevel::GRANT:
return "GRANT";
case PermissionLevel::NEUTRAL:
return "NEUTRAL";
case PermissionLevel::DENY:
return "DENY";
}
}
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)) {
return PermissionLevel::DENY;
}
if (grants_ & utils::UnderlyingCast(permission)) {
return PermissionLevel::GRANT;
}
return PermissionLevel::NEUTRAL;
}
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);
}
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;
}
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()) {
throw AuthException("Couldn't load permissions data!");
}
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
throw AuthException("Couldn't load permissions data!");
}
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();
}
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
Role::Role(const std::string &rolename, const Permissions &permissions)
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
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()) {
throw AuthException("Couldn't load role data!");
}
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
return {data["rolename"], permissions};
}
bool operator==(const Role &first, const Role &second) {
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
return VerifyPassword(password, password_hash_);
}
void User::UpdatePassword(const std::optional<std::string> &password) {
if (!password) {
if (!FLAGS_auth_password_permit_null) {
throw AuthException("Null passwords aren't permitted!");
}
password_hash_ = "";
return;
}
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);
}
void User::SetRole(const Role &role) { role_.emplace(role); }
void User::ClearRole() { role_ = std::nullopt; }
Permissions User::GetPermissions() const {
if (role_) {
return Permissions(permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies());
}
return permissions_;
}
const std::string &User::username() const { return username_; }
const Permissions &User::permissions() const { return permissions_; }
Permissions &User::permissions() { return permissions_; }
const Role *User::role() const {
if (role_.has_value()) {
return &role_.value();
}
return nullptr;
}
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()) {
throw AuthException("Couldn't load user data!");
}
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
return {data["username"], data["password_hash"], permissions};
}
bool operator==(const User &first, const User &second) {
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_;
}
2022-03-09 22:53:33 +08:00
} // namespace memgraph::auth