mirror of
https://github.com/dragonflydb/dragonfly
synced 2024-11-22 15:44:13 +00:00
chore: implement User and UserRegistry classes (#1693)
This commit is contained in:
parent
82c3690e75
commit
e22c131b7c
@ -25,7 +25,7 @@ add_library(dragonfly_lib channel_store.cc command_registry.cc
|
||||
zset_family.cc version.cc bitops_family.cc container_utils.cc io_utils.cc
|
||||
serializer_commons.cc journal/serializer.cc journal/executor.cc journal/streamer.cc
|
||||
top_keys.cc multi_command_squasher.cc hll_family.cc cluster/cluster_config.cc
|
||||
cluster/cluster_family.cc)
|
||||
cluster/cluster_family.cc acl/user.cc acl/user_registry.cc)
|
||||
|
||||
|
||||
cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib aws_lib strings_lib html_lib
|
||||
@ -61,6 +61,7 @@ cxx_test(hll_family_test dfly_test_lib LABELS DFLY)
|
||||
cxx_test(search/search_family_test dfly_test_lib LABELS DFLY)
|
||||
cxx_test(cluster/cluster_config_test dfly_test_lib LABELS DFLY)
|
||||
cxx_test(cluster/cluster_family_test dfly_test_lib LABELS DFLY)
|
||||
cxx_test(acl/user_registry_test dfly_test_lib LABELS DFLY)
|
||||
|
||||
|
||||
|
||||
@ -69,4 +70,4 @@ add_dependencies(check_dfly dragonfly_test json_family_test list_family_test
|
||||
generic_family_test memcache_parser_test rdb_test journal_test
|
||||
redis_parser_test snapshot_test stream_family_test string_family_test
|
||||
bitops_family_test set_family_test zset_family_test hll_family_test
|
||||
cluster_config_test cluster_family_test)
|
||||
cluster_config_test cluster_family_test user_registry_test)
|
||||
|
77
src/server/acl/user.cc
Normal file
77
src/server/acl/user.cc
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2023, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#include "server/acl/user.h"
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace dfly {
|
||||
|
||||
User::User() {
|
||||
// acl_categories_ = AclCat::ACL_CATEGORY_ADMIN;
|
||||
}
|
||||
|
||||
void User::Update(UpdateRequest&& req) {
|
||||
if (req.password) {
|
||||
SetPassword(*req.password);
|
||||
}
|
||||
|
||||
if (req.plus_acl_categories) {
|
||||
SetAclCategories(*req.plus_acl_categories);
|
||||
}
|
||||
|
||||
if (req.minus_acl_categories) {
|
||||
UnsetAclCategories(*req.minus_acl_categories);
|
||||
}
|
||||
|
||||
if (req.is_active) {
|
||||
SetIsActive(*req.is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void User::SetPassword(std::string_view password) {
|
||||
password_ = HashPassword(password);
|
||||
}
|
||||
|
||||
bool User::HasPassword(std::string_view password) const {
|
||||
if (!password_) {
|
||||
if (password == "nopass") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// hash password and compare
|
||||
return *password_ == HashPassword(password);
|
||||
}
|
||||
|
||||
void User::SetAclCategories(uint64_t cat) {
|
||||
acl_categories_ |= cat;
|
||||
}
|
||||
|
||||
void User::UnsetAclCategories(uint64_t cat) {
|
||||
SetAclCategories(cat);
|
||||
acl_categories_ ^= cat;
|
||||
}
|
||||
|
||||
uint32_t User::AclCategory() const {
|
||||
return acl_categories_;
|
||||
}
|
||||
|
||||
// For ACL commands
|
||||
// void SetAclCommand()
|
||||
// void AclCommand() const;
|
||||
|
||||
void User::SetIsActive(bool is_active) {
|
||||
is_active_ = is_active;
|
||||
}
|
||||
|
||||
bool User::IsActive() const {
|
||||
return is_active_;
|
||||
}
|
||||
|
||||
uint32_t User::HashPassword(std::string_view password) const {
|
||||
return XXH3_64bits(password.data(), password.size());
|
||||
}
|
||||
|
||||
} // namespace dfly
|
168
src/server/acl/user.h
Normal file
168
src/server/acl/user.h
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright 2023, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/hash/hash.h"
|
||||
|
||||
namespace dfly {
|
||||
|
||||
class CommandId;
|
||||
|
||||
// TODO implement these
|
||||
//#bool CheckIfCommandAllowed(uint64_t command_id, const CommandId& command);
|
||||
//#bool CheckIfAclCategoryAllowed(uint64_t command_id, const CommandId& command);
|
||||
|
||||
namespace AclCat {
|
||||
/* There are 21 ACL categories as of redis 7
|
||||
*
|
||||
* bit 0: admin
|
||||
* bit 1: bitmap
|
||||
* bit 2: blocking
|
||||
* bit 3: connection
|
||||
* bit 4: dangerous
|
||||
* bit 5: geo
|
||||
* bit 6: hash
|
||||
* bit 7: list
|
||||
* bit 8: set
|
||||
* bit 9: string
|
||||
* bit 10: sorttedset
|
||||
* bit 11: hyperloglog
|
||||
* bit 12: streams
|
||||
* bit 13: fast
|
||||
* bit 14: slow
|
||||
* bit 15: key-space
|
||||
* bit 16: pubsub
|
||||
* bit 17: read
|
||||
* bit 18: write
|
||||
* bit 19: scripting
|
||||
* bit 20: transaction
|
||||
*
|
||||
* The rest of the bitfield, will contain special values like @all
|
||||
*
|
||||
* bits 21..31: tba
|
||||
*/
|
||||
enum AclCat {
|
||||
ACL_CATEGORY_KEYSPACE = 1ULL << 0,
|
||||
ACL_CATEGORY_READ = 1ULL << 1,
|
||||
ACL_CATEGORY_WRITE = 1ULL << 2,
|
||||
ACL_CATEGORY_SET = 1ULL << 3,
|
||||
ACL_CATEGORY_SORTEDSET = 1ULL << 4,
|
||||
ACL_CATEGORY_LIST = 1ULL << 5,
|
||||
ACL_CATEGORY_HASH = 1ULL << 6,
|
||||
ACL_CATEGORY_STRING = 1ULL << 7,
|
||||
ACL_CATEGORY_BITMAP = 1ULL << 8,
|
||||
ACL_CATEGORY_HYPERLOGLOG = 1ULL << 9,
|
||||
ACL_CATEGORY_GEO = 1ULL << 10,
|
||||
ACL_CATEGORY_STREAM = 1ULL << 11,
|
||||
ACL_CATEGORY_PUBSUB = 1ULL << 12,
|
||||
ACL_CATEGORY_ADMIN = 1ULL << 13,
|
||||
ACL_CATEGORY_FAST = 1ULL << 14,
|
||||
ACL_CATEGORY_SLOW = 1ULL << 15,
|
||||
ACL_CATEGORY_BLOCKING = 1ULL << 16,
|
||||
ACL_CATEGORY_DANGEROUS = 1ULL << 17,
|
||||
ACL_CATEGORY_CONNECTION = 1ULL << 18,
|
||||
ACL_CATEGORY_TRANSACTION = 1ULL << 19,
|
||||
ACL_CATEGORY_SCRIPTING = 1ULL << 20
|
||||
};
|
||||
|
||||
// Special flag/mask for all
|
||||
inline constexpr uint32_t ACL_CATEGORY_NONE = 0;
|
||||
inline constexpr uint32_t ACL_CATEGORY_ALL = std::numeric_limits<uint32_t>::max();
|
||||
} // namespace AclCat
|
||||
|
||||
inline const absl::flat_hash_map<std::string_view, uint32_t> CATEGORY_INDEX_TABLE{
|
||||
{"KEYSPACE", AclCat::ACL_CATEGORY_KEYSPACE},
|
||||
{"READ", AclCat::ACL_CATEGORY_READ},
|
||||
{"WRITE", AclCat::ACL_CATEGORY_WRITE},
|
||||
{"SET", AclCat::ACL_CATEGORY_SET},
|
||||
{"SORTED_SET", AclCat::ACL_CATEGORY_SORTEDSET},
|
||||
{"LIST", AclCat::ACL_CATEGORY_LIST},
|
||||
{"HASH", AclCat::ACL_CATEGORY_HASH},
|
||||
{"STRING", AclCat::ACL_CATEGORY_STRING},
|
||||
{"BITMAP", AclCat::ACL_CATEGORY_BITMAP},
|
||||
{"HYPERLOG", AclCat::ACL_CATEGORY_HYPERLOGLOG},
|
||||
{"GEO", AclCat::ACL_CATEGORY_GEO},
|
||||
{"STREAM", AclCat::ACL_CATEGORY_STREAM},
|
||||
{"PUBSUB", AclCat::ACL_CATEGORY_PUBSUB},
|
||||
{"ADMIN", AclCat::ACL_CATEGORY_ADMIN},
|
||||
{"FAST", AclCat::ACL_CATEGORY_FAST},
|
||||
{"SLOW", AclCat::ACL_CATEGORY_SLOW},
|
||||
{"BLOCKING", AclCat::ACL_CATEGORY_BLOCKING},
|
||||
{"DANGEROUS", AclCat::ACL_CATEGORY_DANGEROUS},
|
||||
{"CONNECTION", AclCat::ACL_CATEGORY_CONNECTION},
|
||||
{"TRANSACTION", AclCat::ACL_CATEGORY_TRANSACTION},
|
||||
{"SCRIPTING", AclCat::ACL_CATEGORY_SCRIPTING}};
|
||||
|
||||
class User final {
|
||||
public:
|
||||
struct UpdateRequest {
|
||||
std::optional<std::string> password{};
|
||||
|
||||
std::optional<uint32_t> plus_acl_categories{};
|
||||
std::optional<uint32_t> minus_acl_categories{};
|
||||
|
||||
// DATATYPE_BITSET commands;
|
||||
|
||||
std::optional<bool> is_active{};
|
||||
};
|
||||
|
||||
/* Used for default user
|
||||
* password = nopass
|
||||
* acl_categories = +@all
|
||||
* is_active = true;
|
||||
*/
|
||||
User();
|
||||
|
||||
User(const User&) = delete;
|
||||
User(User&&) = default;
|
||||
|
||||
// For single step updates
|
||||
void Update(UpdateRequest&& req);
|
||||
|
||||
bool HasPassword(std::string_view password) const;
|
||||
|
||||
uint32_t AclCategory() const;
|
||||
|
||||
// TODO
|
||||
// For ACL commands
|
||||
// void SetAclCommand()
|
||||
// void AclCommand() const;
|
||||
|
||||
bool IsActive() const;
|
||||
|
||||
private:
|
||||
// For ACL categories
|
||||
void SetAclCategories(uint64_t cat);
|
||||
void UnsetAclCategories(uint64_t cat);
|
||||
|
||||
// For is_active flag
|
||||
void SetIsActive(bool is_active);
|
||||
|
||||
// Helper function for hashing passwords
|
||||
uint32_t HashPassword(std::string_view password) const;
|
||||
|
||||
// For passwords
|
||||
void SetPassword(std::string_view password);
|
||||
|
||||
// when optional is empty, the special `nopass` password is implied
|
||||
// password hashed with xx64
|
||||
std::optional<uint64_t> password_;
|
||||
uint32_t acl_categories_{AclCat::ACL_CATEGORY_NONE};
|
||||
|
||||
// we have at least 221 commands including a bunch of subcommands
|
||||
// LARGE_BITFIELD_DATATYPE acl_commands_;
|
||||
|
||||
// if the user is on/off
|
||||
bool is_active_{false};
|
||||
};
|
||||
|
||||
} // namespace dfly
|
53
src/server/acl/user_registry.cc
Normal file
53
src/server/acl/user_registry.cc
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2023, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#include "server/acl/user_registry.h"
|
||||
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "core/fibers.h"
|
||||
|
||||
namespace dfly {
|
||||
|
||||
void UserRegistry::MaybeAddAndUpdate(std::string_view username, User::UpdateRequest req) {
|
||||
std::unique_lock<util::SharedMutex> lock(mu_);
|
||||
auto& user = registry_[username];
|
||||
user.Update(std::move(req));
|
||||
}
|
||||
|
||||
void UserRegistry::RemoveUser(std::string_view username) {
|
||||
std::unique_lock<util::SharedMutex> lock(mu_);
|
||||
registry_.erase(username);
|
||||
// TODO evict authed connections from user
|
||||
}
|
||||
|
||||
UserRegistry::UserCredentials UserRegistry::GetCredentials(std::string_view username) const {
|
||||
std::shared_lock<util::SharedMutex> lock(mu_);
|
||||
auto it = registry_.find(username);
|
||||
if (it == registry_.end()) {
|
||||
return {};
|
||||
}
|
||||
return {it->second.AclCategory()};
|
||||
}
|
||||
|
||||
bool UserRegistry::IsUserActive(std::string_view username) const {
|
||||
std::shared_lock<util::SharedMutex> lock(mu_);
|
||||
auto it = registry_.find(username);
|
||||
if (it == registry_.end()) {
|
||||
return false;
|
||||
}
|
||||
return it->second.IsActive();
|
||||
}
|
||||
|
||||
bool UserRegistry::AuthUser(std::string_view username, std::string_view password) const {
|
||||
std::shared_lock<util::SharedMutex> lock(mu_);
|
||||
const auto& user = registry_.find(username);
|
||||
if (user == registry_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return user->second.HasPassword(password);
|
||||
}
|
||||
|
||||
} // namespace dfly
|
57
src/server/acl/user_registry.h
Normal file
57
src/server/acl/user_registry.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2022, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <absl/synchronization/mutex.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/fibers.h"
|
||||
#include "server/acl/user.h"
|
||||
|
||||
namespace dfly {
|
||||
|
||||
class UserRegistry {
|
||||
public:
|
||||
UserRegistry() = default;
|
||||
|
||||
UserRegistry(const UserRegistry&) = delete;
|
||||
UserRegistry(UserRegistry&&) = delete;
|
||||
|
||||
// Acquires a write lock of mu_
|
||||
// If the user with name `username` does not exist, it's added in the store with
|
||||
// the exact fields found in req
|
||||
// If the user exists, the bitfields are updated with a `logical and` operation
|
||||
// TODO change return time to communicate back results to acl commands
|
||||
void MaybeAddAndUpdate(std::string_view username, User::UpdateRequest req);
|
||||
|
||||
// Acquires a write lock on mu_
|
||||
// Removes user from the store
|
||||
// kills already existing connections from the removed user
|
||||
// TODO change return time to communicate back results to acl commands
|
||||
void RemoveUser(std::string_view username);
|
||||
|
||||
struct UserCredentials {
|
||||
uint32_t acl_categories{0};
|
||||
};
|
||||
|
||||
// Acquires a read lock
|
||||
UserCredentials GetCredentials(std::string_view username) const;
|
||||
|
||||
// Acquires a read lock
|
||||
bool IsUserActive(std::string_view username) const;
|
||||
|
||||
// Acquires a read lock
|
||||
// Used by Auth
|
||||
bool AuthUser(std::string_view username, std::string_view password) const;
|
||||
|
||||
private:
|
||||
absl::flat_hash_map<std::string, User> registry_;
|
||||
// TODO add abseil mutex attributes
|
||||
mutable util::SharedMutex mu_;
|
||||
};
|
||||
|
||||
} // namespace dfly
|
45
src/server/acl/user_registry_test.cc
Normal file
45
src/server/acl/user_registry_test.cc
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2022, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#include "server/acl/user_registry.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "base/gtest.h"
|
||||
#include "base/logging.h"
|
||||
#include "server/acl/user.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
namespace dfly {
|
||||
|
||||
class UserRegistryTest : public Test {};
|
||||
|
||||
TEST_F(UserRegistryTest, BasicOp) {
|
||||
UserRegistry registry;
|
||||
const std::string username = "kostas";
|
||||
const std::string pass = "mypass";
|
||||
|
||||
User::UpdateRequest req{pass, {}, {}, {}};
|
||||
registry.MaybeAddAndUpdate(username, std::move(req));
|
||||
CHECK_EQ(registry.AuthUser(username, pass), true);
|
||||
CHECK_EQ(registry.IsUserActive(username), false);
|
||||
|
||||
CHECK_EQ(registry.GetCredentials(username).acl_categories, AclCat::ACL_CATEGORY_NONE);
|
||||
|
||||
const uint32_t set_category = 0 | AclCat::ACL_CATEGORY_LIST | AclCat::ACL_CATEGORY_SET;
|
||||
req = User::UpdateRequest{{}, set_category, {}, {}};
|
||||
registry.MaybeAddAndUpdate(username, std::move(req));
|
||||
auto acl_categories = registry.GetCredentials(username).acl_categories;
|
||||
CHECK_EQ(acl_categories, set_category);
|
||||
|
||||
req = User::UpdateRequest{{}, {}, 0 | AclCat::ACL_CATEGORY_LIST, {}};
|
||||
registry.MaybeAddAndUpdate(username, std::move(req));
|
||||
acl_categories = registry.GetCredentials(username).acl_categories;
|
||||
const uint32_t expected_res = 0 | AclCat::ACL_CATEGORY_SET;
|
||||
CHECK_EQ(acl_categories, expected_res);
|
||||
}
|
||||
|
||||
} // namespace dfly
|
Loading…
Reference in New Issue
Block a user