From 97b1e67d80cc555cc6d4ee2e30a54ebf1c6b8558 Mon Sep 17 00:00:00 2001 From: Gareth Andrew Lloyd <gareth.lloyd@memgraph.io> Date: Tue, 30 Jan 2024 18:17:05 +0000 Subject: [PATCH] Fix auth durability (#1644) * Change auth durability to store hash algorithm * Add Salt to SHA256 --- src/auth/auth.cpp | 61 ++++++++++- src/auth/crypto.cpp | 212 ++++++++++++++++++++++++++++-------- src/auth/crypto.hpp | 43 +++++++- src/auth/models.cpp | 124 +++++++++++++-------- src/auth/models.hpp | 19 +++- src/utils/enum.hpp | 12 ++ tests/unit/auth.cpp | 80 ++++++++------ tests/unit/auth_handler.cpp | 48 ++++---- 8 files changed, 437 insertions(+), 162 deletions(-) diff --git a/src/auth/auth.cpp b/src/auth/auth.cpp index 33d8b2cac..88f0c4410 100644 --- a/src/auth/auth.cpp +++ b/src/auth/auth.cpp @@ -13,6 +13,7 @@ #include <fmt/format.h> +#include "auth/crypto.hpp" #include "auth/exceptions.hpp" #include "license/license.hpp" #include "utils/flag_validation.hpp" @@ -43,6 +44,9 @@ namespace memgraph::auth { const std::string kUserPrefix = "user:"; const std::string kRolePrefix = "role:"; const std::string kLinkPrefix = "link:"; +const std::string kVersion = "version"; + +static constexpr auto kVersionV1 = "V1"; /** * All data stored in the `Auth` storage is stored in an underlying @@ -61,8 +65,59 @@ const std::string kLinkPrefix = "link:"; * key="link:<username>", value="<rolename>" */ +namespace { +void MigrateVersions(kvstore::KVStore &store) { + static constexpr auto kPasswordHashV0V1 = "password_hash"; + auto version_str = store.Get(kVersion); + + if (!version_str) { + using namespace std::string_literals; + + // pre versioning, add version to the store + auto puts = std::map<std::string, std::string>{{kVersion, kVersionV1}}; + + // also add hash kind into durability + + auto it = store.begin(kUserPrefix); + auto const e = store.end(kUserPrefix); + + if (it != e) { + const auto hash_algo = CurrentHashAlgorithm(); + spdlog::info("Updating auth durability, assuming previously stored as {}", AsString(hash_algo)); + + for (; it != e; ++it) { + auto const &[key, value] = *it; + try { + auto user_data = nlohmann::json::parse(value); + + auto password_hash = user_data[kPasswordHashV0V1]; + if (!password_hash.is_string()) { + throw AuthException("Couldn't load user data!"); + } + // upgrade the password_hash to include the hash algortihm + if (password_hash.empty()) { + user_data[kPasswordHashV0V1] = nullptr; + } else { + user_data[kPasswordHashV0V1] = HashedPassword{hash_algo, password_hash}; + } + puts.emplace(key, user_data.dump()); + } catch (const nlohmann::json::parse_error &e) { + throw AuthException("Couldn't load user data!"); + } + } + } + + // Perform migration to V1 + store.PutMultiple(puts); + version_str = kVersionV1; + } +} +}; // namespace + Auth::Auth(std::string storage_directory, Config config) - : storage_(std::move(storage_directory)), module_(FLAGS_auth_module_executable), config_{std::move(config)} {} + : storage_(std::move(storage_directory)), module_(FLAGS_auth_module_executable), config_{std::move(config)} { + MigrateVersions(storage_); +} std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) { if (module_.IsUsed()) { @@ -153,6 +208,10 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s username, "https://memgr.ph/auth")); return std::nullopt; } + if (user->UpgradeHash(password)) { + SaveUser(*user); + } + return user; } } diff --git a/src/auth/crypto.cpp b/src/auth/crypto.cpp index c433eaf62..a8351635a 100644 --- a/src/auth/crypto.cpp +++ b/src/auth/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -22,10 +22,14 @@ namespace { using namespace std::literals; -inline constexpr std::array password_encryption_mappings{ - std::pair{"bcrypt"sv, memgraph::auth::PasswordEncryptionAlgorithm::BCRYPT}, - std::pair{"sha256"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256}, - std::pair{"sha256-multiple"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256_MULTIPLE}}; + +constexpr auto kHashAlgo = "hash_algo"; +constexpr auto kPasswordHash = "password_hash"; + +inline constexpr std::array password_hash_mappings{ + std::pair{"bcrypt"sv, memgraph::auth::PasswordHashAlgorithm::BCRYPT}, + std::pair{"sha256"sv, memgraph::auth::PasswordHashAlgorithm::SHA256}, + std::pair{"sha256-multiple"sv, memgraph::auth::PasswordHashAlgorithm::SHA256_MULTIPLE}}; inline constexpr uint64_t ONE_SHA_ITERATION = 1; inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024; @@ -35,7 +39,7 @@ inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024; DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", "The password encryption algorithm used for authentication.", { if (const auto result = - memgraph::utils::IsValidEnumValueString(value, password_encryption_mappings); + memgraph::utils::IsValidEnumValueString(value, password_hash_mappings); result.HasError()) { const auto error = result.GetError(); switch (error) { @@ -45,7 +49,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", } case memgraph::utils::ValidationError::InvalidValue: { std::cout << "Invalid value for password encryption algorithm. Allowed values: " - << memgraph::utils::GetAllowedEnumValuesString(password_encryption_mappings) + << memgraph::utils::GetAllowedEnumValuesString(password_hash_mappings) << std::endl; break; } @@ -58,7 +62,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", namespace memgraph::auth { namespace BCrypt { -std::string EncryptPassword(const std::string &password) { +std::string HashPassword(const std::string &password) { char salt[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE]; @@ -86,16 +90,30 @@ bool VerifyPassword(const std::string &password, const std::string &hash) { } // namespace BCrypt namespace SHA { + +namespace { + +constexpr auto SHA_LENGTH = 64U; +constexpr auto SALT_SIZE = 16U; +constexpr auto SALT_SIZE_DURABLE = SALT_SIZE * 2; + #if OPENSSL_VERSION_MAJOR >= 3 -std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t number_of_iterations, + std::string_view salt) { unsigned char hash[SHA256_DIGEST_LENGTH]; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr); EVP_DigestInit_ex(ctx, md, nullptr); + + if (!salt.empty()) { + DMG_ASSERT(salt.size() == SALT_SIZE); + EVP_DigestUpdate(ctx, salt.data(), salt.size()); + } + for (auto i = 0; i < number_of_iterations; i++) { - EVP_DigestUpdate(ctx, password.c_str(), password.size()); + EVP_DigestUpdate(ctx, password.data(), password.size()); } EVP_DigestFinal_ex(ctx, hash, nullptr); @@ -103,6 +121,11 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t EVP_MD_CTX_free(ctx); std::stringstream result_stream; + + for (unsigned char salt_char : salt) { + result_stream << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU); + } + for (auto hash_char : hash) { result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char; } @@ -110,17 +133,27 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t return result_stream.str(); } #else -std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t number_of_iterations, + std::string_view salt) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha256; SHA256_Init(&sha256); + + if (!salt.empty()) { + DMG_ASSERT(salt.size() == SALT_SIZE); + SHA256_Update(&sha256, salt.data(), salt.size()); + } + for (auto i = 0; i < number_of_iterations; i++) { - SHA256_Update(&sha256, password.c_str(), password.size()); + SHA256_Update(&sha256, password.data(), password.size()); } SHA256_Final(hash, &sha256); std::stringstream ss; + for (unsigned char salt_char : salt) { + ss << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU); + } for (auto hash_char : hash) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char; } @@ -129,55 +162,144 @@ std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_ } #endif -std::string EncryptPassword(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPassword(std::string_view password, const uint64_t number_of_iterations, std::string_view salt) { #if OPENSSL_VERSION_MAJOR >= 3 - return EncryptPasswordOpenSSL3(password, number_of_iterations); + return HashPasswordOpenSSL3(password, number_of_iterations, salt); #else - return EncryptPasswordOpenSSL1_1(password, number_of_iterations); + return HashPasswordOpenSSL1_1(password, number_of_iterations, salt); #endif } -bool VerifyPassword(const std::string &password, const std::string &hash, const uint64_t number_of_iterations) { - auto password_hash = EncryptPassword(password, number_of_iterations); +auto ExtractSalt(std::string_view salt_durable) -> std::array<char, SALT_SIZE> { + static_assert(SALT_SIZE_DURABLE % 2 == 0); + static_assert(SALT_SIZE_DURABLE / 2 == SALT_SIZE); + + MG_ASSERT(salt_durable.size() == SALT_SIZE_DURABLE); + auto const *b = salt_durable.cbegin(); + auto const *const e = salt_durable.cend(); + + auto salt = std::array<char, SALT_SIZE>{}; + auto *inserter = salt.begin(); + + auto const toval = [](char a) -> uint8_t { + if ('0' <= a && a <= '9') { + return a - '0'; + } + if ('a' <= a && a <= 'f') { + return 10 + (a - 'a'); + } + MG_ASSERT(false, "Currupt hash, can't extract salt"); + __builtin_unreachable(); + }; + + for (; b != e; b += 2, ++inserter) { + *inserter = static_cast<char>(static_cast<uint8_t>(toval(b[0]) << 4U) | toval(b[1])); + } + return salt; +} + +bool IsSalted(std::string_view hash) { return hash.size() == SHA_LENGTH + SALT_SIZE_DURABLE; } + +bool VerifyPassword(std::string_view password, std::string_view hash, const uint64_t number_of_iterations) { + auto password_hash = std::invoke([&] { + if (hash.size() == SHA_LENGTH) [[unlikely]] { + // Just SHA256 + return HashPassword(password, number_of_iterations, {}); + } else { + // SHA256 + SALT + MG_ASSERT(IsSalted(hash)); + auto const salt_durable = std::string_view{hash.data(), SALT_SIZE_DURABLE}; + std::array<char, SALT_SIZE> salt = ExtractSalt(salt_durable); + return HashPassword(password, number_of_iterations, {salt.data(), salt.size()}); + } + }); return password_hash == hash; } + +} // namespace + } // namespace SHA -bool VerifyPassword(const std::string &password, const std::string &hash) { - const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>( - FLAGS_password_encryption_algorithm, password_encryption_mappings); +HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo) { + auto const hash_algo = override_algo.value_or(CurrentHashAlgorithm()); + auto password_hash = std::invoke([&] { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: { + return BCrypt::HashPassword(password); + } + case PasswordHashAlgorithm::SHA256: + case PasswordHashAlgorithm::SHA256_MULTIPLE: { + auto gen = std::mt19937(std::random_device{}()); + auto salt = std::array<char, SHA::SALT_SIZE>{}; + auto dis = std::uniform_int_distribution<unsigned char>(0, 255); + std::generate(salt.begin(), salt.end(), [&]() { return dis(gen); }); + auto iterations = (hash_algo == PasswordHashAlgorithm::SHA256) ? ONE_SHA_ITERATION : MULTIPLE_SHA_ITERATIONS; + return SHA::HashPassword(password, iterations, {salt.data(), salt.size()}); + } + } + }); + return HashedPassword{hash_algo, std::move(password_hash)}; +}; - if (!password_encryption_algorithm.has_value()) { - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); +namespace { + +auto InternalParseHashAlgorithm(std::string_view algo) -> PasswordHashAlgorithm { + auto maybe_parsed = utils::StringToEnum<PasswordHashAlgorithm>(algo, password_hash_mappings); + if (!maybe_parsed) { + throw AuthException("Invalid password encryption '{}'!", algo); } - - switch (password_encryption_algorithm.value()) { - case PasswordEncryptionAlgorithm::BCRYPT: - return BCrypt::VerifyPassword(password, hash); - case PasswordEncryptionAlgorithm::SHA256: - return SHA::VerifyPassword(password, hash, ONE_SHA_ITERATION); - case PasswordEncryptionAlgorithm::SHA256_MULTIPLE: - return SHA::VerifyPassword(password, hash, MULTIPLE_SHA_ITERATIONS); - } - - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); + return *maybe_parsed; } -std::string EncryptPassword(const std::string &password) { - const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>( - FLAGS_password_encryption_algorithm, password_encryption_mappings); +PasswordHashAlgorithm &InternalCurrentHashAlgorithm() { + static auto current = PasswordHashAlgorithm::BCRYPT; + static std::once_flag flag; + std::call_once(flag, [] { current = InternalParseHashAlgorithm(FLAGS_password_encryption_algorithm); }); + return current; +} +} // namespace - if (!password_encryption_algorithm.has_value()) { - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); +auto CurrentHashAlgorithm() -> PasswordHashAlgorithm { return InternalCurrentHashAlgorithm(); } + +void SetHashAlgorithm(std::string_view algo) { + auto ¤t = InternalCurrentHashAlgorithm(); + current = InternalParseHashAlgorithm(algo); +} + +auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view { + return *utils::EnumToString<PasswordHashAlgorithm>(hash_algo, password_hash_mappings); +} + +bool HashedPassword::VerifyPassword(const std::string &password) { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: + return BCrypt::VerifyPassword(password, password_hash); + case PasswordHashAlgorithm::SHA256: + return SHA::VerifyPassword(password, password_hash, ONE_SHA_ITERATION); + case PasswordHashAlgorithm::SHA256_MULTIPLE: + return SHA::VerifyPassword(password, password_hash, MULTIPLE_SHA_ITERATIONS); } +} - switch (password_encryption_algorithm.value()) { - case PasswordEncryptionAlgorithm::BCRYPT: - return BCrypt::EncryptPassword(password); - case PasswordEncryptionAlgorithm::SHA256: - return SHA::EncryptPassword(password, ONE_SHA_ITERATION); - case PasswordEncryptionAlgorithm::SHA256_MULTIPLE: - return SHA::EncryptPassword(password, MULTIPLE_SHA_ITERATIONS); +void to_json(nlohmann::json &j, const HashedPassword &p) { + j = nlohmann::json{{kHashAlgo, p.hash_algo}, {kPasswordHash, p.password_hash}}; +} + +void from_json(const nlohmann::json &j, HashedPassword &p) { + // NOLINTNEXTLINE(cppcoreguidelines-init-variables) + PasswordHashAlgorithm hash_algo; + j.at(kHashAlgo).get_to(hash_algo); + auto password_hash = j.value(kPasswordHash, std::string()); + p = HashedPassword{hash_algo, std::move(password_hash)}; +} + +bool HashedPassword::IsSalted() const { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: + return true; + case PasswordHashAlgorithm::SHA256: + case PasswordHashAlgorithm::SHA256_MULTIPLE: + return SHA::IsSalted(password_hash); } } diff --git a/src/auth/crypto.hpp b/src/auth/crypto.hpp index dbceb128b..c5dfc1c05 100644 --- a/src/auth/crypto.hpp +++ b/src/auth/crypto.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -8,14 +8,45 @@ #pragma once +#include <json/json.hpp> +#include <optional> #include <string> namespace memgraph::auth { -enum class PasswordEncryptionAlgorithm : uint8_t { BCRYPT, SHA256, SHA256_MULTIPLE }; +/// Need to be stable, auth durability depends on this +enum class PasswordHashAlgorithm : uint8_t { BCRYPT = 0, SHA256 = 1, SHA256_MULTIPLE = 2 }; -/// @throw AuthException if unable to encrypt the password. -std::string EncryptPassword(const std::string &password); +void SetHashAlgorithm(std::string_view algo); -/// @throw AuthException if unable to verify the password. -bool VerifyPassword(const std::string &password, const std::string &hash); +auto CurrentHashAlgorithm() -> PasswordHashAlgorithm; + +auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view; + +struct HashedPassword { + HashedPassword() = default; + HashedPassword(PasswordHashAlgorithm hash_algo, std::string password_hash) + : hash_algo{hash_algo}, password_hash{std::move(password_hash)} {} + HashedPassword(HashedPassword const &) = default; + HashedPassword(HashedPassword &&) = default; + HashedPassword &operator=(HashedPassword const &) = default; + HashedPassword &operator=(HashedPassword &&) = default; + + friend bool operator==(HashedPassword const &, HashedPassword const &) = default; + + bool VerifyPassword(const std::string &password); + + bool IsSalted() const; + + auto HashAlgo() const -> PasswordHashAlgorithm { return hash_algo; } + + friend void to_json(nlohmann::json &j, const HashedPassword &p); + friend void from_json(const nlohmann::json &j, HashedPassword &p); + + private: + PasswordHashAlgorithm hash_algo{PasswordHashAlgorithm::BCRYPT}; + std::string password_hash{}; +}; + +/// @throw AuthException if unable to hash the password. +HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo = {}); } // namespace memgraph::auth diff --git a/src/auth/models.cpp b/src/auth/models.cpp index 7ded6410d..a59a73c7b 100644 --- a/src/auth/models.cpp +++ b/src/auth/models.cpp @@ -25,6 +25,21 @@ namespace memgraph::auth { namespace { +constexpr auto kRoleName = "rolename"; +constexpr auto kPermissions = "permissions"; +constexpr auto kGrants = "grants"; +constexpr auto kDenies = "denies"; +constexpr auto kUsername = "username"; +constexpr auto kPasswordHash = "password_hash"; + +#ifdef MG_ENTERPRISE +constexpr auto kGlobalPermission = "global_permission"; +constexpr auto kFineGrainedAccessHandler = "fine_grained_access_handler"; +constexpr auto kAllowAll = "allow_all"; +constexpr auto kDefault = "default"; +constexpr auto kDatabases = "databases"; +#endif + // Constant list of all available permissions. const std::vector<Permission> kPermissionsAll = {Permission::MATCH, Permission::CREATE, @@ -233,8 +248,9 @@ std::vector<Permission> Permissions::GetDenies() const { nlohmann::json Permissions::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["grants"] = grants_; - data["denies"] = denies_; + + data[kGrants] = grants_; + data[kDenies] = denies_; return data; } @@ -242,10 +258,10 @@ 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()) { + if (!data[kGrants].is_number_unsigned() || !data[kDenies].is_number_unsigned()) { throw AuthException("Couldn't load permissions data!"); } - return Permissions{data["grants"], data["denies"]}; + return Permissions{data[kGrants], data[kDenies]}; } uint64_t Permissions::grants() const { return grants_; } @@ -307,8 +323,8 @@ nlohmann::json FineGrainedAccessPermissions::Serialize() const { return {}; } nlohmann::json data = nlohmann::json::object(); - data["permissions"] = permissions_; - data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1; + data[kPermissions] = permissions_; + data[kGlobalPermission] = global_permission_.has_value() ? global_permission_.value() : -1; return data; } @@ -321,13 +337,13 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo } std::optional<uint64_t> global_permission; - if (data["global_permission"].empty() || data["global_permission"] == -1) { + if (data[kGlobalPermission].empty() || data[kGlobalPermission] == -1) { global_permission = std::nullopt; } else { - global_permission = data["global_permission"]; + global_permission = data[kGlobalPermission]; } - return FineGrainedAccessPermissions(data["permissions"], global_permission); + return FineGrainedAccessPermissions(data[kPermissions], global_permission); } const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const { @@ -433,13 +449,13 @@ const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermission nlohmann::json Role::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["rolename"] = rolename_; - data["permissions"] = permissions_.Serialize(); + data[kRoleName] = rolename_; + data[kPermissions] = permissions_.Serialize(); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { - data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize(); + data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize(); } else { - data["fine_grained_access_handler"] = {}; + data[kFineGrainedAccessHandler] = {}; } #endif return data; @@ -449,21 +465,21 @@ 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()) { + if (!data[kRoleName].is_string() || !data[kPermissions].is_object()) { throw AuthException("Couldn't load role data!"); } - auto permissions = Permissions::Deserialize(data["permissions"]); + auto permissions = Permissions::Deserialize(data[kPermissions]); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { FineGrainedAccessHandler fine_grained_access_handler; // We can have an empty fine_grained if the user was created without a valid license - if (data["fine_grained_access_handler"].is_object()) { - fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]); + if (data[kFineGrainedAccessHandler].is_object()) { + fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]); } - return {data["rolename"], permissions, std::move(fine_grained_access_handler)}; + return {data[kRoleName], permissions, std::move(fine_grained_access_handler)}; } #endif - return {data["rolename"], permissions}; + return {data[kRoleName], permissions}; } bool operator==(const Role &first, const Role &second) { @@ -533,10 +549,10 @@ const std::string &Databases::GetDefault() const { nlohmann::json Databases::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["grants"] = grants_dbs_; - data["denies"] = denies_dbs_; - data["allow_all"] = allow_all_; - data["default"] = default_db_; + data[kGrants] = grants_dbs_; + data[kDenies] = denies_dbs_; + data[kAllowAll] = allow_all_; + data[kDefault] = default_db_; return data; } @@ -544,22 +560,22 @@ Databases Databases::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load database data!"); } - if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() || - !data["default"].is_string()) { + if (!data[kGrants].is_structured() || !data[kDenies].is_structured() || !data[kAllowAll].is_boolean() || + !data[kDefault].is_string()) { throw AuthException("Couldn't load database data!"); } - return {data["allow_all"], data["grants"], data["denies"], data["default"]}; + return {data[kAllowAll], data[kGrants], data[kDenies], data[kDefault]}; } #endif User::User() = default; User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {} -User::User(const std::string &username, std::string password_hash, const Permissions &permissions) +User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions) : username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), permissions_(permissions) {} #ifdef MG_ENTERPRISE -User::User(const std::string &username, std::string password_hash, const Permissions &permissions, +User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions, FineGrainedAccessHandler fine_grained_access_handler, Databases db_access) : username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), @@ -569,16 +585,16 @@ User::User(const std::string &username, std::string password_hash, const Permiss #endif bool User::CheckPassword(const std::string &password) { - if (password_hash_.empty()) return true; - return VerifyPassword(password, password_hash_); + return password_hash_ ? password_hash_->VerifyPassword(password) : true; } -void User::UpdatePassword(const std::optional<std::string> &password) { +void User::UpdatePassword(const std::optional<std::string> &password, + std::optional<PasswordHashAlgorithm> algo_override) { if (!password) { - password_hash_ = ""; + password_hash_.reset(); return; } - password_hash_ = EncryptPassword(*password); + password_hash_ = HashPassword(*password, algo_override); } void User::SetRole(const Role &role) { role_.emplace(role); } @@ -637,16 +653,20 @@ const Role *User::role() const { nlohmann::json User::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["username"] = username_; - data["password_hash"] = password_hash_; - data["permissions"] = permissions_.Serialize(); + data[kUsername] = username_; + if (password_hash_.has_value()) { + data[kPasswordHash] = *password_hash_; + } else { + data[kPasswordHash] = nullptr; + } + data[kPermissions] = permissions_.Serialize(); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { - data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize(); - data["databases"] = database_access_.Serialize(); + data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize(); + data[kDatabases] = database_access_.Serialize(); } else { - data["fine_grained_access_handler"] = {}; - data["databases"] = {}; + data[kFineGrainedAccessHandler] = {}; + data[kDatabases] = {}; } #endif // The role shouldn't be serialized here, it is stored as a foreign key. @@ -657,15 +677,23 @@ 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()) { + auto password_hash_json = data[kPasswordHash]; + if (!data[kUsername].is_string() || !(password_hash_json.is_object() || password_hash_json.is_null()) || + !data[kPermissions].is_object()) { throw AuthException("Couldn't load user data!"); } - auto permissions = Permissions::Deserialize(data["permissions"]); + + std::optional<HashedPassword> password_hash{}; + if (password_hash_json.is_object()) { + password_hash = password_hash_json.get<HashedPassword>(); + } + + auto permissions = Permissions::Deserialize(data[kPermissions]); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { Databases db_access; - if (data["databases"].is_structured()) { - db_access = Databases::Deserialize(data["databases"]); + if (data[kDatabases].is_structured()) { + db_access = Databases::Deserialize(data[kDatabases]); } else { // Back-compatibility spdlog::warn("User without specified database access. Given access to the default database."); @@ -674,13 +702,13 @@ User User::Deserialize(const nlohmann::json &data) { } FineGrainedAccessHandler fine_grained_access_handler; // We can have an empty fine_grained if the user was created without a valid license - if (data["fine_grained_access_handler"].is_object()) { - fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]); + if (data[kFineGrainedAccessHandler].is_object()) { + fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]); } - return {data["username"], data["password_hash"], permissions, std::move(fine_grained_access_handler), db_access}; + return {data[kUsername], std::move(password_hash), permissions, std::move(fine_grained_access_handler), db_access}; } #endif - return {data["username"], data["password_hash"], permissions}; + return {data[kUsername], std::move(password_hash), permissions}; } bool operator==(const User &first, const User &second) { diff --git a/src/auth/models.hpp b/src/auth/models.hpp index 4af635e3c..bb6dd2a7a 100644 --- a/src/auth/models.hpp +++ b/src/auth/models.hpp @@ -15,6 +15,7 @@ #include <json/json.hpp> #include <utility> +#include "crypto.hpp" #include "dbms/constants.hpp" #include "utils/logging.hpp" @@ -332,9 +333,9 @@ class User final { User(); explicit User(const std::string &username); - User(const std::string &username, std::string password_hash, const Permissions &permissions); + User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions); #ifdef MG_ENTERPRISE - User(const std::string &username, std::string password_hash, const Permissions &permissions, + User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions, FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {}); #endif User(const User &) = default; @@ -346,8 +347,18 @@ class User final { /// @throw AuthException if unable to verify the password. bool CheckPassword(const std::string &password); + bool UpgradeHash(const std::string password) { + if (!password_hash_) return false; + if (password_hash_->IsSalted()) return false; + + auto const algo = password_hash_->HashAlgo(); + UpdatePassword(password, algo); + return true; + } + /// @throw AuthException if unable to set the password. - void UpdatePassword(const std::optional<std::string> &password = std::nullopt); + void UpdatePassword(const std::optional<std::string> &password = {}, + std::optional<PasswordHashAlgorithm> algo_override = std::nullopt); void SetRole(const Role &role); @@ -382,7 +393,7 @@ class User final { private: std::string username_; - std::string password_hash_; + std::optional<HashedPassword> password_hash_; Permissions permissions_; #ifdef MG_ENTERPRISE FineGrainedAccessHandler fine_grained_access_handler_; diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp index 4f1ffbd0d..50a6d4621 100644 --- a/src/utils/enum.hpp +++ b/src/utils/enum.hpp @@ -55,6 +55,18 @@ std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) { return mapping_iter->second; } +// Tries to convert a enum into string, which would then contain a value if the conversion +// has been successful. +template <typename Enum> +auto EnumToString(const auto &value, const auto &mappings) -> std::optional<std::string_view> { + const auto mapping_iter = + std::find_if(mappings.begin(), mappings.end(), [&](const auto &mapping) { return mapping.second == value; }); + if (mapping_iter == mappings.cend()) { + return std::nullopt; + } + return mapping_iter->first; +} + template <typename T, typename Enum> requires std::integral<T> inline T EnumToNum(Enum res) { diff --git a/tests/unit/auth.cpp b/tests/unit/auth.cpp index 3c2931a77..bc2947a12 100644 --- a/tests/unit/auth.cpp +++ b/tests/unit/auth.cpp @@ -666,6 +666,17 @@ TEST(AuthWithoutStorage, UserSerializeDeserialize) { ASSERT_EQ(user, output); } +TEST(AuthWithoutStorage, UserSerializeDeserializeWithOutPassword) { + auto user = User("test"); + user.permissions().Grant(Permission::MATCH); + user.permissions().Deny(Permission::MERGE); + + auto data = user.Serialize(); + + auto output = User::Deserialize(data); + ASSERT_EQ(user, output); +} + TEST(AuthWithoutStorage, RoleSerializeDeserialize) { auto role = Role("test"); role.permissions().Grant(Permission::MATCH); @@ -716,8 +727,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) { { auto perms = Permissions(); auto fine_grained_access_handler = FineGrainedAccessHandler(); - auto user1 = User("test", "pw", perms, fine_grained_access_handler); - auto user2 = User("Test", "pw", perms, fine_grained_access_handler); + auto passwordHash = HashPassword("pw"); + auto user1 = User("test", passwordHash, perms, fine_grained_access_handler); + auto user2 = User("Test", passwordHash, perms, fine_grained_access_handler); ASSERT_EQ(user1, user2); ASSERT_EQ(user1.username(), user2.username()); ASSERT_EQ(user1.username(), "test"); @@ -920,51 +932,50 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { } TEST(AuthWithoutStorage, Crypto) { - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } class AuthWithVariousEncryptionAlgorithms : public ::testing::Test { protected: - void SetUp() override { FLAGS_password_encryption_algorithm = "bcrypt"; } + void SetUp() override { SetHashAlgorithm("bcrypt"); } }; TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordDefault) { - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256) { - FLAGS_password_encryption_algorithm = "sha256"; - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + SetHashAlgorithm("sha256"); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256_1024) { - FLAGS_password_encryption_algorithm = "sha256-multiple"; - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + SetHashAlgorithm("sha256-multiple"); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } -TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordThrow) { - FLAGS_password_encryption_algorithm = "abcd"; - ASSERT_THROW(EncryptPassword("hello"), AuthException); +TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmNonsenseThrow) { + ASSERT_THROW(SetHashAlgorithm("abcd"), AuthException); } -TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordEmptyEncryptionThrow) { - FLAGS_password_encryption_algorithm = ""; - ASSERT_THROW(EncryptPassword("hello"), AuthException); +TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmEmptyThrow) { + ASSERT_THROW(SetHashAlgorithm(""), AuthException); } class AuthWithStorageWithVariousEncryptionAlgorithms : public ::testing::Test { protected: void SetUp() override { memgraph::utils::EnsureDir(test_folder_); - FLAGS_password_encryption_algorithm = "bcrypt"; + SetHashAlgorithm("bcrypt"); + memgraph::license::global_license_checker.EnableTesting(); } @@ -982,25 +993,26 @@ TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserDefault) { } TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256) { - FLAGS_password_encryption_algorithm = "sha256"; + SetHashAlgorithm("sha256"); auto user = auth.AddUser("Alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256_1024) { - FLAGS_password_encryption_algorithm = "sha256-multiple"; + SetHashAlgorithm("sha256-multiple"); auto user = auth.AddUser("Alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } -TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserThrow) { - FLAGS_password_encryption_algorithm = "abcd"; - ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException); -} - -TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserEmptyPasswordEncryptionThrow) { - FLAGS_password_encryption_algorithm = ""; - ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException); +TEST(Serialize, HashedPassword) { + for (auto algo : + {PasswordHashAlgorithm::BCRYPT, PasswordHashAlgorithm::SHA256, PasswordHashAlgorithm::SHA256_MULTIPLE}) { + auto sut = HashPassword("password", algo); + nlohmann::json j = sut; + auto ret = j.get<HashedPassword>(); + ASSERT_EQ(sut, ret); + ASSERT_TRUE(ret.VerifyPassword("password")); + } } diff --git a/tests/unit/auth_handler.cpp b/tests/unit/auth_handler.cpp index 1230fe4ba..a162d1838 100644 --- a/tests/unit/auth_handler.cpp +++ b/tests/unit/auth_handler.cpp @@ -57,7 +57,7 @@ TEST_F(AuthQueryHandlerFixture, GivenAuthQueryHandlerWhenInitializedHaveNoUserna } TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturned) { - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); { ASSERT_EQ(auth_handler.GetUsernames().size(), 1); } @@ -71,7 +71,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturn TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturned) { perms.Grant(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -92,7 +92,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturne TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned) { perms.Deny(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -114,7 +114,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned TEST_F(AuthQueryHandlerFixture, GivenUserWhenPrivilegeRevokedThenNothingIsReturned) { perms.Deny(memgraph::auth::Permission::MATCH); perms.Revoke(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -180,7 +180,7 @@ TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeRevokedThenNothingIsReturn TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedTwoPrivilegesThenBothAreReturned) { perms.Grant(memgraph::auth::Permission::MATCH); perms.Grant(memgraph::auth::Permission::CREATE); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -191,7 +191,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherGrantedThe perms.Grant(memgraph::auth::Permission::MATCH); memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms}; auth->SaveRole(role); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; user.SetRole(role); auth->SaveUser(user); @@ -215,7 +215,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherDeniedThenB perms.Deny(memgraph::auth::Permission::MATCH); memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms}; auth->SaveRole(role); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; user.SetRole(role); auth->SaveUser(user); @@ -245,7 +245,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherDeniedThen user_perms.Grant(memgraph::auth::Permission::MATCH); memgraph::auth::User user = memgraph::auth::User{ user_name, - "", + std::nullopt, user_perms, }; user.SetRole(role); @@ -275,7 +275,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherGrantedThen memgraph::auth::Permissions user_perms{}; user_perms.Deny(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", user_perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, user_perms}; user.SetRole(role); auth->SaveUser(user); @@ -305,7 +305,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnLabelThenIsDispla memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -334,7 +334,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnLabelThe memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -364,7 +364,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnLabelThenTopO memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -392,7 +392,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnLabelThenIs memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -421,7 +421,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnLa memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -451,7 +451,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnLabelTh memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -480,7 +480,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnEdgeTypeThenIsDis memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -509,7 +509,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnEdgeType memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -539,7 +539,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnEdgeTypeThenT memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -567,7 +567,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnEdgeTypeThe memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -597,7 +597,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnEd }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -627,7 +627,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnEdgeTyp memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -656,7 +656,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnLabelThenNoPermis memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -685,7 +685,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnEdgeTypeThenNoPer memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -714,7 +714,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedReadAndDeniedUpdateThenOneIs memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name);