Implement leftover Auth queries
Reviewers: mtomic, buda Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1535
This commit is contained in:
parent
1febc15d68
commit
94ad18326c
@ -1,6 +1,6 @@
|
||||
#include "auth/auth.hpp"
|
||||
|
||||
#include "utils/string.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
|
||||
namespace auth {
|
||||
|
||||
@ -44,7 +44,7 @@ std::experimental::optional<User> Auth::GetUser(const std::string &username) {
|
||||
try {
|
||||
data = nlohmann::json::parse(*existing_user);
|
||||
} catch (const nlohmann::json::parse_error &e) {
|
||||
throw utils::BasicException("Couldn't load user data!");
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
|
||||
auto user = User::Deserialize(data);
|
||||
@ -59,38 +59,59 @@ std::experimental::optional<User> Auth::GetUser(const std::string &username) {
|
||||
return user;
|
||||
}
|
||||
|
||||
bool Auth::SaveUser(const User &user) {
|
||||
if (!storage_.Put(kUserPrefix + user.username(), user.Serialize().dump())) {
|
||||
return false;
|
||||
}
|
||||
void Auth::SaveUser(const User &user) {
|
||||
bool success = false;
|
||||
if (user.role()) {
|
||||
return storage_.Put(kLinkPrefix + user.username(), user.role()->rolename());
|
||||
success = storage_.PutMultiple(
|
||||
{{kUserPrefix + user.username(), user.Serialize().dump()},
|
||||
{kLinkPrefix + user.username(), user.role()->rolename()}});
|
||||
} else {
|
||||
return storage_.Delete(kLinkPrefix + user.username());
|
||||
success = storage_.PutAndDeleteMultiple(
|
||||
{{kUserPrefix + user.username(), user.Serialize().dump()}},
|
||||
{kLinkPrefix + user.username()});
|
||||
}
|
||||
if (!success) {
|
||||
throw AuthException("Couldn't save user '{}'!", user.username());
|
||||
}
|
||||
}
|
||||
|
||||
std::experimental::optional<User> Auth::AddUser(const std::string &username) {
|
||||
std::experimental::optional<User> Auth::AddUser(
|
||||
const std::string &username,
|
||||
const std::experimental::optional<std::string> &password) {
|
||||
auto existing_user = GetUser(username);
|
||||
if (existing_user) return std::experimental::nullopt;
|
||||
auto existing_role = GetRole(username);
|
||||
if (existing_role) return std::experimental::nullopt;
|
||||
auto new_user = User(username);
|
||||
if (!SaveUser(new_user)) return std::experimental::nullopt;
|
||||
new_user.UpdatePassword(password);
|
||||
SaveUser(new_user);
|
||||
return new_user;
|
||||
}
|
||||
|
||||
bool Auth::RemoveUser(const std::string &username) {
|
||||
if (!storage_.Get(kUserPrefix + username)) return false;
|
||||
if (!storage_.Delete(kLinkPrefix + username)) return false;
|
||||
return storage_.Delete(kUserPrefix + username);
|
||||
std::vector<std::string> keys(
|
||||
{kLinkPrefix + username, kUserPrefix + username});
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove user '{}'!", username);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<auth::User> Auth::AllUsers() {
|
||||
std::vector<auth::User> ret;
|
||||
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix);
|
||||
++it) {
|
||||
auto user = GetUser(it->first.substr(kUserPrefix.size()));
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Auth::HasUsers() {
|
||||
for (auto it = storage_.begin(); it != storage_.end(); ++it) {
|
||||
if (utils::StartsWith(it->first, kUserPrefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return storage_.begin(kUserPrefix) != storage_.end(kUserPrefix);
|
||||
}
|
||||
|
||||
std::experimental::optional<Role> Auth::GetRole(const std::string &rolename) {
|
||||
@ -101,36 +122,74 @@ std::experimental::optional<Role> Auth::GetRole(const std::string &rolename) {
|
||||
try {
|
||||
data = nlohmann::json::parse(*existing_role);
|
||||
} catch (const nlohmann::json::parse_error &e) {
|
||||
throw utils::BasicException("Couldn't load role data!");
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
|
||||
return Role::Deserialize(data);
|
||||
}
|
||||
|
||||
bool Auth::SaveRole(const Role &role) {
|
||||
return storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump());
|
||||
void Auth::SaveRole(const Role &role) {
|
||||
if (!storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump())) {
|
||||
throw AuthException("Couldn't save role '{}'!", role.rolename());
|
||||
}
|
||||
}
|
||||
|
||||
std::experimental::optional<Role> Auth::AddRole(const std::string &rolename) {
|
||||
auto existing_role = GetRole(rolename);
|
||||
if (existing_role) return std::experimental::nullopt;
|
||||
auto existing_user = GetUser(rolename);
|
||||
if (existing_user) return std::experimental::nullopt;
|
||||
auto new_role = Role(rolename);
|
||||
if (!SaveRole(new_role)) return std::experimental::nullopt;
|
||||
SaveRole(new_role);
|
||||
return new_role;
|
||||
}
|
||||
|
||||
bool Auth::RemoveRole(const std::string &rolename) {
|
||||
if (!storage_.Get(kRolePrefix + rolename)) return false;
|
||||
std::vector<std::string> links;
|
||||
for (auto it = storage_.begin(); it != storage_.end(); ++it) {
|
||||
if (utils::StartsWith(it->first, kLinkPrefix) && it->second == rolename) {
|
||||
links.push_back(it->first);
|
||||
std::vector<std::string> keys;
|
||||
for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix);
|
||||
++it) {
|
||||
if (it->second == rolename) {
|
||||
keys.push_back(it->first);
|
||||
}
|
||||
}
|
||||
for (const auto &link : links) {
|
||||
storage_.Delete(link);
|
||||
keys.push_back(kRolePrefix + rolename);
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove role '{}'!", rolename);
|
||||
}
|
||||
return storage_.Delete(kRolePrefix + rolename);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<auth::Role> Auth::AllRoles() {
|
||||
std::vector<auth::Role> ret;
|
||||
for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix);
|
||||
++it) {
|
||||
auto rolename = it->first.substr(kRolePrefix.size());
|
||||
auto role = GetRole(rolename);
|
||||
if (role) {
|
||||
ret.push_back(*role);
|
||||
} else {
|
||||
throw AuthException("Couldn't load role '{}'!", rolename);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename) {
|
||||
std::vector<auth::User> ret;
|
||||
for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix);
|
||||
++it) {
|
||||
auto username = it->first.substr(kLinkPrefix.size());
|
||||
if (it->second == rolename) {
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
} else {
|
||||
throw AuthException("Couldn't load user '{}'!", username);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::mutex &Auth::WithLock() { return lock_; }
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
#include <experimental/optional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "storage/kvstore.hpp"
|
||||
|
||||
@ -18,27 +20,125 @@ class Auth final {
|
||||
public:
|
||||
Auth(const std::string &storage_directory);
|
||||
|
||||
/**
|
||||
* Authenticates a user using his username and password.
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
*
|
||||
* @return a user when the username and password match, nullopt otherwise
|
||||
*/
|
||||
std::experimental::optional<User> Authenticate(const std::string &username,
|
||||
const std::string &password);
|
||||
|
||||
/**
|
||||
* Gets a user from the storage.
|
||||
*
|
||||
* @param username
|
||||
*
|
||||
* @return a user when the user exists, nullopt otherwise
|
||||
*/
|
||||
std::experimental::optional<User> GetUser(const std::string &username);
|
||||
|
||||
bool SaveUser(const User &user);
|
||||
/**
|
||||
* Saves a user object to the storage.
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
void SaveUser(const User &user);
|
||||
|
||||
std::experimental::optional<User> AddUser(const std::string &username);
|
||||
/**
|
||||
* Creates a user if the user doesn't exist.
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
*
|
||||
* @return a user when the user is created, nullopt if the user exists
|
||||
*/
|
||||
std::experimental::optional<User> AddUser(
|
||||
const std::string &username,
|
||||
const std::experimental::optional<std::string> &password =
|
||||
std::experimental::nullopt);
|
||||
|
||||
/**
|
||||
* Removes a user from the storage.
|
||||
*
|
||||
* @param username
|
||||
*
|
||||
* @return `true` if the user existed and was removed, `false` if the user
|
||||
* doesn't exist
|
||||
*/
|
||||
bool RemoveUser(const std::string &username);
|
||||
|
||||
/**
|
||||
* Gets all users from the storage.
|
||||
*
|
||||
* @return a list of users
|
||||
*/
|
||||
std::vector<User> AllUsers();
|
||||
|
||||
/**
|
||||
* Returns whether there are users in the storage.
|
||||
*
|
||||
* @return `true` if the storage contains any users, `false` otherwise
|
||||
*/
|
||||
bool HasUsers();
|
||||
|
||||
/**
|
||||
* Gets a role from the storage.
|
||||
*
|
||||
* @param rolename
|
||||
*
|
||||
* @return a role when the role exists, nullopt otherwise
|
||||
*/
|
||||
std::experimental::optional<Role> GetRole(const std::string &rolename);
|
||||
|
||||
bool SaveRole(const Role &role);
|
||||
/**
|
||||
* Saves a role object to the storage.
|
||||
*
|
||||
* @param role
|
||||
*/
|
||||
void SaveRole(const Role &role);
|
||||
|
||||
/**
|
||||
* Creates a role if the role doesn't exist.
|
||||
*
|
||||
* @param rolename
|
||||
*
|
||||
* @return a role when the role is created, nullopt if the role exists
|
||||
*/
|
||||
std::experimental::optional<Role> AddRole(const std::string &rolename);
|
||||
|
||||
/**
|
||||
* Removes a role from the storage.
|
||||
*
|
||||
* @param rolename
|
||||
*
|
||||
* @return `true` if the role existed and was removed, `false` if the role
|
||||
* doesn't exist
|
||||
*/
|
||||
bool RemoveRole(const std::string &rolename);
|
||||
|
||||
/**
|
||||
* Gets all roles from the storage.
|
||||
*
|
||||
* @return a list of roles
|
||||
*/
|
||||
std::vector<Role> AllRoles();
|
||||
|
||||
/**
|
||||
* Gets all users for a role from the storage.
|
||||
*
|
||||
* @param rolename
|
||||
*
|
||||
* @return a list of roles
|
||||
*/
|
||||
std::vector<User> AllUsersForRole(const std::string &rolename);
|
||||
|
||||
/**
|
||||
* Returns a reference to the lock that should be used for all operations that
|
||||
* require more than one interaction with this class.
|
||||
*/
|
||||
std::mutex &WithLock();
|
||||
|
||||
private:
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <libbcrypt/bcrypt.h>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
|
||||
namespace auth {
|
||||
|
||||
@ -14,11 +14,11 @@ const std::string EncryptPassword(const std::string &password) {
|
||||
// its default value of `12`. Increasing the workfactor increases the time
|
||||
// needed to generate the salt.
|
||||
if (bcrypt_gensalt(-1, salt) != 0) {
|
||||
throw utils::BasicException("Couldn't generate hashing salt!");
|
||||
throw AuthException("Couldn't generate hashing salt!");
|
||||
}
|
||||
|
||||
if (bcrypt_hashpw(password.c_str(), salt, hash) != 0) {
|
||||
throw utils::BasicException("Couldn't hash password!");
|
||||
throw AuthException("Couldn't hash password!");
|
||||
}
|
||||
|
||||
return std::string(hash);
|
||||
@ -27,7 +27,7 @@ const std::string EncryptPassword(const std::string &password) {
|
||||
bool VerifyPassword(const std::string &password, const std::string &hash) {
|
||||
int ret = bcrypt_checkpw(password.c_str(), hash.c_str());
|
||||
if (ret == -1) {
|
||||
throw utils::BasicException("Couldn't check password!");
|
||||
throw AuthException("Couldn't check password!");
|
||||
}
|
||||
return ret == 0;
|
||||
}
|
||||
|
15
src/auth/exceptions.hpp
Normal file
15
src/auth/exceptions.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace auth {
|
||||
|
||||
/**
|
||||
* This exception class is thrown for all exceptions that can occur when dealing
|
||||
* with the Auth library.
|
||||
*/
|
||||
class AuthException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
} // namespace auth
|
@ -1,11 +1,45 @@
|
||||
#include "auth/models.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "auth/crypto.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
DEFINE_bool(auth_password_permit_null, true,
|
||||
"Set to false to disable null passwords.");
|
||||
|
||||
DEFINE_string(auth_password_strength_regex, ".+",
|
||||
"The regular expression that should be used to match the entire "
|
||||
"entered password to ensure its strength.");
|
||||
|
||||
namespace auth {
|
||||
|
||||
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::AUTH:
|
||||
return "AUTH";
|
||||
case Permission::STREAM:
|
||||
return "STREAM";
|
||||
}
|
||||
}
|
||||
|
||||
Permissions::Permissions(uint64_t grants, uint64_t denies) {
|
||||
// The deny bitmask has higher priority than the grant bitmask.
|
||||
denies_ = denies;
|
||||
@ -16,11 +50,11 @@ Permissions::Permissions(uint64_t grants, uint64_t 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;
|
||||
return PermissionLevel::DENY;
|
||||
} else if (grants_ & utils::UnderlyingCast(permission)) {
|
||||
return PermissionLevel::Grant;
|
||||
return PermissionLevel::GRANT;
|
||||
}
|
||||
return PermissionLevel::Neutral;
|
||||
return PermissionLevel::NEUTRAL;
|
||||
}
|
||||
|
||||
void Permissions::Grant(Permission permission) {
|
||||
@ -44,6 +78,26 @@ void Permissions::Deny(Permission permission) {
|
||||
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_;
|
||||
@ -53,11 +107,11 @@ nlohmann::json Permissions::Serialize() const {
|
||||
|
||||
Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw utils::BasicException("Couldn't load permissions data!");
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
if (!data["grants"].is_number_unsigned() ||
|
||||
!data["denies"].is_number_unsigned()) {
|
||||
throw utils::BasicException("Couldn't load permissions data!");
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
return {data["grants"], data["denies"]};
|
||||
}
|
||||
@ -91,10 +145,10 @@ nlohmann::json Role::Serialize() const {
|
||||
|
||||
Role Role::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw utils::BasicException("Couldn't load role data!");
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
|
||||
throw utils::BasicException("Couldn't load role data!");
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
return {data["rolename"], permissions};
|
||||
@ -114,15 +168,33 @@ User::User(const std::string &username, const std::string &password_hash,
|
||||
permissions_(permissions) {}
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
if (password_hash_ == "") return true;
|
||||
return VerifyPassword(password, password_hash_);
|
||||
}
|
||||
|
||||
void User::UpdatePassword(const std::string &password) {
|
||||
password_hash_ = EncryptPassword(password);
|
||||
void User::UpdatePassword(
|
||||
const std::experimental::optional<std::string> &password) {
|
||||
if (password) {
|
||||
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);
|
||||
} else {
|
||||
if (!FLAGS_auth_password_permit_null) {
|
||||
throw AuthException("Null passwords aren't permitted!");
|
||||
}
|
||||
password_hash_ = "";
|
||||
}
|
||||
}
|
||||
|
||||
void User::SetRole(const Role &role) { role_.emplace(role); }
|
||||
|
||||
void User::ClearRole() { role_ = std::experimental::nullopt; }
|
||||
|
||||
const Permissions User::GetPermissions() const {
|
||||
if (role_) {
|
||||
return Permissions(permissions_.grants() | role_->permissions().grants(),
|
||||
@ -148,11 +220,11 @@ nlohmann::json User::Serialize() const {
|
||||
|
||||
User User::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw utils::BasicException("Couldn't load user data!");
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
if (!data["username"].is_string() || !data["password_hash"].is_string() ||
|
||||
!data["permissions"].is_object()) {
|
||||
throw utils::BasicException("Couldn't load user data!");
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
return {data["username"], data["password_hash"], permissions};
|
||||
|
@ -7,25 +7,36 @@
|
||||
|
||||
namespace auth {
|
||||
|
||||
// TODO (mferencevic): Add permissions for admin actions.
|
||||
// These permissions must have values that are applicable for usage in a
|
||||
// bitmask.
|
||||
enum class Permission : uint64_t {
|
||||
Read = 0x00000001,
|
||||
|
||||
Create = 0x00000002,
|
||||
|
||||
Update = 0x00000004,
|
||||
|
||||
Delete = 0x00000008,
|
||||
MATCH = 0x00000001,
|
||||
CREATE = 0x00000002,
|
||||
MERGE = 0x00000004,
|
||||
DELETE = 0x00000008,
|
||||
SET = 0x00000010,
|
||||
REMOVE = 0x00000020,
|
||||
INDEX = 0x00000040,
|
||||
AUTH = 0x00010000,
|
||||
STREAM = 0x00020000,
|
||||
};
|
||||
|
||||
// Constant list of all available permissions.
|
||||
const std::vector<Permission> kPermissionsAll = {
|
||||
Permission::MATCH, Permission::CREATE, Permission::MERGE,
|
||||
Permission::DELETE, Permission::SET, Permission::REMOVE,
|
||||
Permission::INDEX, Permission::AUTH, Permission::STREAM};
|
||||
|
||||
// Function that converts a permission to its string representation.
|
||||
std::string PermissionToString(Permission permission);
|
||||
|
||||
// Class that indicates what permission level the user/role has.
|
||||
enum class PermissionLevel {
|
||||
Grant,
|
||||
Neutral,
|
||||
Deny,
|
||||
GRANT,
|
||||
NEUTRAL,
|
||||
DENY,
|
||||
};
|
||||
|
||||
// TODO (mferencevic): Add string conversions to/from permissions.
|
||||
|
||||
class Permissions final {
|
||||
public:
|
||||
Permissions(uint64_t grants = 0, uint64_t denies = 0);
|
||||
@ -38,6 +49,10 @@ class Permissions final {
|
||||
|
||||
void Deny(Permission permission);
|
||||
|
||||
std::vector<Permission> GetGrants() const;
|
||||
|
||||
std::vector<Permission> GetDenies() const;
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
static Permissions Deserialize(const nlohmann::json &data);
|
||||
@ -77,7 +92,6 @@ class Role final {
|
||||
|
||||
bool operator==(const Role &first, const Role &second);
|
||||
|
||||
// TODO (mferencevic): Implement password strength enforcement.
|
||||
// TODO (mferencevic): Implement password expiry.
|
||||
class User final {
|
||||
public:
|
||||
@ -88,10 +102,13 @@ class User final {
|
||||
|
||||
bool CheckPassword(const std::string &password);
|
||||
|
||||
void UpdatePassword(const std::string &password);
|
||||
void UpdatePassword(const std::experimental::optional<std::string> &password =
|
||||
std::experimental::nullopt);
|
||||
|
||||
void SetRole(const Role &role);
|
||||
|
||||
void ClearRole();
|
||||
|
||||
const Permissions GetPermissions() const;
|
||||
|
||||
const std::string &username() const;
|
||||
|
@ -421,8 +421,10 @@ struct AuthQuery {
|
||||
match @2;
|
||||
merge @3;
|
||||
set @4;
|
||||
auth @5;
|
||||
stream @6;
|
||||
remove @5;
|
||||
index @6;
|
||||
auth @7;
|
||||
stream @8;
|
||||
}
|
||||
action @0 :Action;
|
||||
user @1 :Text;
|
||||
|
@ -2146,6 +2146,12 @@ void AuthQuery::Save(capnp::AuthQuery::Builder *builder,
|
||||
case Privilege::SET:
|
||||
privileges_builder.set(i, capnp::AuthQuery::Privilege::SET);
|
||||
break;
|
||||
case Privilege::REMOVE:
|
||||
privileges_builder.set(i, capnp::AuthQuery::Privilege::REMOVE);
|
||||
break;
|
||||
case Privilege::INDEX:
|
||||
privileges_builder.set(i, capnp::AuthQuery::Privilege::INDEX);
|
||||
break;
|
||||
case Privilege::AUTH:
|
||||
privileges_builder.set(i, capnp::AuthQuery::Privilege::AUTH);
|
||||
break;
|
||||
@ -2232,6 +2238,12 @@ void AuthQuery::Load(const capnp::Tree::Reader &base_reader,
|
||||
case capnp::AuthQuery::Privilege::SET:
|
||||
privileges_.push_back(Privilege::SET);
|
||||
break;
|
||||
case capnp::AuthQuery::Privilege::REMOVE:
|
||||
privileges_.push_back(Privilege::REMOVE);
|
||||
break;
|
||||
case capnp::AuthQuery::Privilege::INDEX:
|
||||
privileges_.push_back(Privilege::INDEX);
|
||||
break;
|
||||
case capnp::AuthQuery::Privilege::AUTH:
|
||||
privileges_.push_back(Privilege::AUTH);
|
||||
break;
|
||||
|
@ -2361,7 +2361,19 @@ class AuthQuery : public Clause {
|
||||
SHOW_USERS_FOR_ROLE
|
||||
};
|
||||
|
||||
enum class Privilege { CREATE, DELETE, MATCH, MERGE, SET, AUTH, STREAM };
|
||||
// When adding new privileges, please add them to the `kPrivilegesAll`
|
||||
// constant.
|
||||
enum class Privilege {
|
||||
CREATE,
|
||||
DELETE,
|
||||
MATCH,
|
||||
MERGE,
|
||||
SET,
|
||||
REMOVE,
|
||||
INDEX,
|
||||
AUTH,
|
||||
STREAM
|
||||
};
|
||||
|
||||
Action action_;
|
||||
std::string user_;
|
||||
@ -2405,6 +2417,14 @@ class AuthQuery : public Clause {
|
||||
std::vector<int> *loaded_uids) override;
|
||||
};
|
||||
|
||||
// Constant that holds all available privileges.
|
||||
const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM};
|
||||
|
||||
class CreateStream : public Clause {
|
||||
friend class AstStorage;
|
||||
|
||||
|
@ -386,8 +386,13 @@ antlrcpp::Any CypherMainVisitor::visitGrantPrivilege(
|
||||
AuthQuery *auth = storage_.Create<AuthQuery>();
|
||||
auth->action_ = AuthQuery::Action::GRANT_PRIVILEGE;
|
||||
auth->user_or_role_ = ctx->userOrRole->accept(this).as<std::string>();
|
||||
for (auto *privilege : ctx->privilegeList()->privilege()) {
|
||||
auth->privileges_.push_back(privilege->accept(this));
|
||||
if (ctx->privilegeList()) {
|
||||
for (auto *privilege : ctx->privilegeList()->privilege()) {
|
||||
auth->privileges_.push_back(privilege->accept(this));
|
||||
}
|
||||
} else {
|
||||
/* grant all privileges */
|
||||
auth->privileges_ = kPrivilegesAll;
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
@ -400,8 +405,13 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(
|
||||
AuthQuery *auth = storage_.Create<AuthQuery>();
|
||||
auth->action_ = AuthQuery::Action::DENY_PRIVILEGE;
|
||||
auth->user_or_role_ = ctx->userOrRole->accept(this).as<std::string>();
|
||||
for (auto *privilege : ctx->privilegeList()->privilege()) {
|
||||
auth->privileges_.push_back(privilege->accept(this));
|
||||
if (ctx->privilegeList()) {
|
||||
for (auto *privilege : ctx->privilegeList()->privilege()) {
|
||||
auth->privileges_.push_back(privilege->accept(this));
|
||||
}
|
||||
} else {
|
||||
/* deny all privileges */
|
||||
auth->privileges_ = kPrivilegesAll;
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
@ -420,11 +430,7 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(
|
||||
}
|
||||
} else {
|
||||
/* revoke all privileges */
|
||||
auth->privileges_ = {
|
||||
AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM};
|
||||
auth->privileges_ = kPrivilegesAll;
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
@ -439,6 +445,8 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(
|
||||
if (ctx->MATCH()) return AuthQuery::Privilege::MATCH;
|
||||
if (ctx->MERGE()) return AuthQuery::Privilege::MERGE;
|
||||
if (ctx->SET()) return AuthQuery::Privilege::SET;
|
||||
if (ctx->REMOVE()) return AuthQuery::Privilege::REMOVE;
|
||||
if (ctx->INDEX()) return AuthQuery::Privilege::INDEX;
|
||||
if (ctx->AUTH()) return AuthQuery::Privilege::AUTH;
|
||||
if (ctx->STREAM()) return AuthQuery::Privilege::STREAM;
|
||||
LOG(FATAL) << "Should not get here - unknown privilege!";
|
||||
|
@ -88,14 +88,14 @@ grantRole : GRANT ROLE role=userOrRoleName TO user=userOrRoleName ;
|
||||
|
||||
revokeRole : REVOKE ROLE role=userOrRoleName FROM user=userOrRoleName ;
|
||||
|
||||
grantPrivilege : GRANT privilegeList TO userOrRole=userOrRoleName ;
|
||||
grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
|
||||
|
||||
denyPrivilege : DENY privilegeList TO userOrRole=userOrRoleName ;
|
||||
denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
|
||||
|
||||
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ;
|
||||
|
||||
privilege : CREATE | DELETE | MATCH | MERGE | SET
|
||||
| AUTH | STREAM ;
|
||||
| REMOVE | INDEX | AUTH | STREAM ;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
|
||||
|
@ -3893,22 +3893,108 @@ std::unique_ptr<Cursor> PullRemoteOrderBy::MakeCursor(
|
||||
AuthHandler::AuthHandler(AuthQuery::Action action, std::string user,
|
||||
std::string role, std::string user_or_role,
|
||||
Expression *password,
|
||||
std::vector<AuthQuery::Privilege> privileges)
|
||||
std::vector<AuthQuery::Privilege> privileges,
|
||||
Symbol user_symbol, Symbol role_symbol,
|
||||
Symbol grants_symbol)
|
||||
: action_(action),
|
||||
user_(user),
|
||||
role_(role),
|
||||
user_or_role_(user_or_role),
|
||||
password_(password),
|
||||
privileges_(privileges) {}
|
||||
privileges_(privileges),
|
||||
user_symbol_(user_symbol),
|
||||
role_symbol_(role_symbol),
|
||||
grants_symbol_(grants_symbol) {}
|
||||
|
||||
bool AuthHandler::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
|
||||
return visitor.Visit(*this);
|
||||
}
|
||||
|
||||
std::vector<Symbol> AuthHandler::OutputSymbols(const SymbolTable &) const {
|
||||
switch (action_) {
|
||||
case AuthQuery::Action::SHOW_USERS:
|
||||
case AuthQuery::Action::SHOW_USERS_FOR_ROLE:
|
||||
return {user_symbol_};
|
||||
|
||||
case AuthQuery::Action::SHOW_ROLES:
|
||||
case AuthQuery::Action::SHOW_ROLE_FOR_USER:
|
||||
return {role_symbol_};
|
||||
|
||||
case AuthQuery::Action::SHOW_GRANTS:
|
||||
return {grants_symbol_};
|
||||
|
||||
case AuthQuery::Action::CREATE_USER:
|
||||
case AuthQuery::Action::DROP_USER:
|
||||
case AuthQuery::Action::SET_PASSWORD:
|
||||
case AuthQuery::Action::CREATE_ROLE:
|
||||
case AuthQuery::Action::DROP_ROLE:
|
||||
case AuthQuery::Action::GRANT_ROLE:
|
||||
case AuthQuery::Action::REVOKE_ROLE:
|
||||
case AuthQuery::Action::GRANT_PRIVILEGE:
|
||||
case AuthQuery::Action::DENY_PRIVILEGE:
|
||||
case AuthQuery::Action::REVOKE_PRIVILEGE:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class AuthHandlerCursor : public Cursor {
|
||||
public:
|
||||
AuthHandlerCursor(const AuthHandler &self) : self_(self) {}
|
||||
|
||||
std::vector<auth::Permission> GetAuthPermissions() {
|
||||
std::vector<auth::Permission> ret;
|
||||
for (const auto &privilege : self_.privileges()) {
|
||||
switch (privilege) {
|
||||
case AuthQuery::Privilege::MATCH:
|
||||
ret.push_back(auth::Permission::MATCH);
|
||||
break;
|
||||
case AuthQuery::Privilege::CREATE:
|
||||
ret.push_back(auth::Permission::CREATE);
|
||||
break;
|
||||
case AuthQuery::Privilege::MERGE:
|
||||
ret.push_back(auth::Permission::MERGE);
|
||||
break;
|
||||
case AuthQuery::Privilege::DELETE:
|
||||
ret.push_back(auth::Permission::DELETE);
|
||||
break;
|
||||
case AuthQuery::Privilege::SET:
|
||||
ret.push_back(auth::Permission::SET);
|
||||
break;
|
||||
case AuthQuery::Privilege::REMOVE:
|
||||
ret.push_back(auth::Permission::REMOVE);
|
||||
break;
|
||||
case AuthQuery::Privilege::INDEX:
|
||||
ret.push_back(auth::Permission::INDEX);
|
||||
break;
|
||||
case AuthQuery::Privilege::AUTH:
|
||||
ret.push_back(auth::Permission::AUTH);
|
||||
break;
|
||||
case AuthQuery::Privilege::STREAM:
|
||||
ret.push_back(auth::Permission::STREAM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetGrantsFromAuthPermissions(
|
||||
auth::Permissions &permissions) {
|
||||
std::vector<std::string> grants, denies, ret;
|
||||
for (const auto &permission : permissions.GetGrants()) {
|
||||
grants.push_back(auth::PermissionToString(permission));
|
||||
}
|
||||
for (const auto &permission : permissions.GetDenies()) {
|
||||
denies.push_back(auth::PermissionToString(permission));
|
||||
}
|
||||
if (grants.size() > 0) {
|
||||
ret.push_back(fmt::format("GRANT {}", utils::Join(grants, ", ")));
|
||||
}
|
||||
if (denies.size() > 0) {
|
||||
ret.push_back(fmt::format("DENY {}", utils::Join(denies, ", ")));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Pull(Frame &frame, Context &ctx) override {
|
||||
if (ctx.in_explicit_transaction_) {
|
||||
throw UserModificationInMulticommandTxException();
|
||||
@ -3916,37 +4002,32 @@ class AuthHandlerCursor : public Cursor {
|
||||
|
||||
ExpressionEvaluator evaluator(frame, &ctx, GraphView::OLD);
|
||||
std::experimental::optional<std::string> password;
|
||||
/* TODO(mferencevic): handle null passwords properly */
|
||||
if (self_.password()) {
|
||||
auto password_tv = self_.password()->Accept(evaluator);
|
||||
if (!password_tv.IsString()) {
|
||||
throw QueryRuntimeException("Password must be a string, not '{}'!",
|
||||
password_tv.type());
|
||||
if (!password_tv.IsString() && !password_tv.IsNull()) {
|
||||
throw QueryRuntimeException(
|
||||
"Password must be a string or null, not '{}'!", password_tv.type());
|
||||
}
|
||||
if (password_tv.IsString()) {
|
||||
password = password_tv.ValueString();
|
||||
}
|
||||
password = password_tv.ValueString();
|
||||
}
|
||||
|
||||
auto &auth = *ctx.auth_;
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
|
||||
switch (self_.action()) {
|
||||
case AuthQuery::Action::CREATE_USER: {
|
||||
if (!password) {
|
||||
throw QueryRuntimeException(
|
||||
"Password must be provided when creating a user!");
|
||||
}
|
||||
auto user = auth.AddUser(self_.user());
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.AddUser(self_.user(), password);
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' already exists!",
|
||||
throw QueryRuntimeException("User or role '{}' already exists!",
|
||||
self_.user());
|
||||
}
|
||||
user->UpdatePassword(*password);
|
||||
if (!auth.SaveUser(*user)) {
|
||||
throw QueryRuntimeException("Couldn't save user '{}'!", self_.user());
|
||||
}
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::DROP_USER: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user());
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
|
||||
@ -3955,38 +4036,216 @@ class AuthHandlerCursor : public Cursor {
|
||||
throw QueryRuntimeException("Couldn't remove user '{}'!",
|
||||
self_.user());
|
||||
}
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SET_PASSWORD: {
|
||||
if (!password) {
|
||||
throw QueryRuntimeException("Password must be provided!");
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user());
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
|
||||
}
|
||||
user->UpdatePassword(*password);
|
||||
if (!auth.SaveUser(*user)) {
|
||||
throw QueryRuntimeException("Couldn't set password for user '{}'!",
|
||||
self_.user());
|
||||
}
|
||||
break;
|
||||
user->UpdatePassword(password);
|
||||
auth.SaveUser(*user);
|
||||
return false;
|
||||
}
|
||||
case AuthQuery::Action::CREATE_ROLE:
|
||||
case AuthQuery::Action::DROP_ROLE:
|
||||
case AuthQuery::Action::SHOW_ROLES:
|
||||
case AuthQuery::Action::SHOW_USERS:
|
||||
case AuthQuery::Action::GRANT_ROLE:
|
||||
case AuthQuery::Action::REVOKE_ROLE:
|
||||
|
||||
case AuthQuery::Action::CREATE_ROLE: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto role = auth.AddRole(self_.role());
|
||||
if (!role) {
|
||||
throw QueryRuntimeException("User or role '{}' already exists!",
|
||||
self_.role());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::DROP_ROLE: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto role = auth.GetRole(self_.role());
|
||||
if (!role) {
|
||||
throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
|
||||
}
|
||||
if (!auth.RemoveRole(self_.role())) {
|
||||
throw QueryRuntimeException("Couldn't remove role '{}'!",
|
||||
self_.role());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SHOW_USERS: {
|
||||
if (!users_) {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
users_.emplace(auth.AllUsers());
|
||||
users_it_ = users_->begin();
|
||||
}
|
||||
|
||||
if (users_it_ == users_->end()) return false;
|
||||
|
||||
frame[self_.user_symbol()] = users_it_->username();
|
||||
users_it_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SHOW_ROLES: {
|
||||
if (!roles_) {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
roles_.emplace(auth.AllRoles());
|
||||
roles_it_ = roles_->begin();
|
||||
}
|
||||
|
||||
if (roles_it_ == roles_->end()) return false;
|
||||
|
||||
frame[self_.role_symbol()] = roles_it_->rolename();
|
||||
roles_it_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::GRANT_ROLE: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user());
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
|
||||
}
|
||||
auto role = auth.GetRole(self_.role());
|
||||
if (!role) {
|
||||
throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
|
||||
}
|
||||
if (user->role()) {
|
||||
throw QueryRuntimeException(
|
||||
"User '{}' is already a member of role '{}'!", self_.user(),
|
||||
user->role()->rolename());
|
||||
}
|
||||
user->SetRole(*role);
|
||||
auth.SaveUser(*user);
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::REVOKE_ROLE: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user());
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
|
||||
}
|
||||
auto role = auth.GetRole(self_.role());
|
||||
if (!role) {
|
||||
throw QueryRuntimeException("Role '{}' doesn't exist!", self_.role());
|
||||
}
|
||||
if (user->role() != role) {
|
||||
throw QueryRuntimeException("User '{}' isn't a member of role '{}'!",
|
||||
self_.user(), self_.role());
|
||||
}
|
||||
user->ClearRole();
|
||||
auth.SaveUser(*user);
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::GRANT_PRIVILEGE:
|
||||
case AuthQuery::Action::DENY_PRIVILEGE:
|
||||
case AuthQuery::Action::REVOKE_PRIVILEGE:
|
||||
case AuthQuery::Action::SHOW_GRANTS:
|
||||
case AuthQuery::Action::SHOW_ROLE_FOR_USER:
|
||||
case AuthQuery::Action::SHOW_USERS_FOR_ROLE:
|
||||
throw utils::NotYetImplemented("user auth");
|
||||
case AuthQuery::Action::REVOKE_PRIVILEGE: {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user_or_role());
|
||||
auto role = auth.GetRole(self_.user_or_role());
|
||||
if (!user && !role) {
|
||||
throw QueryRuntimeException("User or role '{}' doesn't exist!",
|
||||
self_.user_or_role());
|
||||
}
|
||||
auto permissions = GetAuthPermissions();
|
||||
if (user) {
|
||||
for (const auto &permission : permissions) {
|
||||
// TODO (mferencevic): should we first check that the privilege
|
||||
// is granted/denied/revoked before unconditionally
|
||||
// granting/denying/revoking it?
|
||||
if (self_.action() == AuthQuery::Action::GRANT_PRIVILEGE) {
|
||||
user->permissions().Grant(permission);
|
||||
} else if (self_.action() == AuthQuery::Action::DENY_PRIVILEGE) {
|
||||
user->permissions().Deny(permission);
|
||||
} else {
|
||||
user->permissions().Revoke(permission);
|
||||
}
|
||||
}
|
||||
auth.SaveUser(*user);
|
||||
} else {
|
||||
for (const auto &permission : permissions) {
|
||||
// TODO (mferencevic): should we first check that the privilege
|
||||
// is granted/denied/revoked before unconditionally
|
||||
// granting/denying/revoking it?
|
||||
if (self_.action() == AuthQuery::Action::GRANT_PRIVILEGE) {
|
||||
role->permissions().Grant(permission);
|
||||
} else if (self_.action() == AuthQuery::Action::DENY_PRIVILEGE) {
|
||||
role->permissions().Deny(permission);
|
||||
} else {
|
||||
role->permissions().Revoke(permission);
|
||||
}
|
||||
}
|
||||
auth.SaveRole(*role);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SHOW_GRANTS: {
|
||||
if (!grants_) {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user_or_role());
|
||||
auto role = auth.GetRole(self_.user_or_role());
|
||||
if (!user && !role) {
|
||||
throw QueryRuntimeException("User or role '{}' doesn't exist!",
|
||||
self_.user_or_role());
|
||||
}
|
||||
if (user) {
|
||||
grants_.emplace(GetGrantsFromAuthPermissions(user->permissions()));
|
||||
} else {
|
||||
grants_.emplace(GetGrantsFromAuthPermissions(role->permissions()));
|
||||
}
|
||||
grants_it_ = grants_->begin();
|
||||
}
|
||||
|
||||
if (grants_it_ == grants_->end()) return false;
|
||||
|
||||
frame[self_.grants_symbol()] = *grants_it_;
|
||||
grants_it_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SHOW_ROLE_FOR_USER: {
|
||||
if (returned_role_for_user_) return false;
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto user = auth.GetUser(self_.user());
|
||||
if (!user) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist!", self_.user());
|
||||
}
|
||||
if (user->role()) {
|
||||
frame[self_.role_symbol()] = user->role()->rolename();
|
||||
} else {
|
||||
frame[self_.role_symbol()] = TypedValue::Null;
|
||||
}
|
||||
returned_role_for_user_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
case AuthQuery::Action::SHOW_USERS_FOR_ROLE: {
|
||||
if (!users_) {
|
||||
std::lock_guard<std::mutex> lock(auth.WithLock());
|
||||
auto role = auth.GetRole(self_.role());
|
||||
if (!role) {
|
||||
throw QueryRuntimeException("Role '{}' doesn't exist!",
|
||||
self_.role());
|
||||
}
|
||||
users_.emplace(auth.AllUsersForRole(self_.role()));
|
||||
users_it_ = users_->begin();
|
||||
}
|
||||
|
||||
if (users_it_ == users_->end()) return false;
|
||||
|
||||
frame[self_.user_symbol()] = users_it_->username();
|
||||
users_it_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
@ -3995,6 +4254,13 @@ class AuthHandlerCursor : public Cursor {
|
||||
|
||||
private:
|
||||
const AuthHandler &self_;
|
||||
std::experimental::optional<std::vector<auth::User>> users_;
|
||||
std::vector<auth::User>::iterator users_it_;
|
||||
std::experimental::optional<std::vector<auth::Role>> roles_;
|
||||
std::vector<auth::Role>::iterator roles_it_;
|
||||
std::experimental::optional<std::vector<std::string>> grants_;
|
||||
std::vector<std::string>::iterator grants_it_;
|
||||
bool returned_role_for_user_{false};
|
||||
};
|
||||
|
||||
std::unique_ptr<Cursor> AuthHandler::MakeCursor(
|
||||
|
@ -2019,6 +2019,12 @@ and returns true, once.")
|
||||
case AuthQuery::Privilege::SET:
|
||||
${builder}.set(i, query::capnp::AuthQuery::Privilege::SET);
|
||||
break;
|
||||
case AuthQuery::Privilege::REMOVE:
|
||||
${builder}.set(i, query::capnp::AuthQuery::Privilege::REMOVE);
|
||||
break;
|
||||
case AuthQuery::Privilege::INDEX:
|
||||
${builder}.set(i, query::capnp::AuthQuery::Privilege::INDEX);
|
||||
break;
|
||||
case AuthQuery::Privilege::AUTH:
|
||||
${builder}.set(i, query::capnp::AuthQuery::Privilege::AUTH);
|
||||
break;
|
||||
@ -2048,6 +2054,12 @@ and returns true, once.")
|
||||
case query::capnp::AuthQuery::Privilege::SET:
|
||||
${member-name}.push_back(AuthQuery::Privilege::SET);
|
||||
break;
|
||||
case query::capnp::AuthQuery::Privilege::REMOVE:
|
||||
${member-name}.push_back(AuthQuery::Privilege::REMOVE);
|
||||
break;
|
||||
case query::capnp::AuthQuery::Privilege::INDEX:
|
||||
${member-name}.push_back(AuthQuery::Privilege::INDEX);
|
||||
break;
|
||||
case query::capnp::AuthQuery::Privilege::AUTH:
|
||||
${member-name}.push_back(AuthQuery::Privilege::AUTH);
|
||||
break;
|
||||
@ -2056,16 +2068,21 @@ and returns true, once.")
|
||||
break;
|
||||
}
|
||||
}
|
||||
cpp<#)))
|
||||
cpp<#))
|
||||
(user-symbol "Symbol" :reader t)
|
||||
(role-symbol "Symbol" :reader t)
|
||||
(grants-symbol "Symbol" :reader t))
|
||||
(:public
|
||||
#>cpp
|
||||
AuthHandler(AuthQuery::Action action, std::string user, std::string role,
|
||||
std::string user_or_role, Expression * password,
|
||||
std::vector<AuthQuery::Privilege> privileges);
|
||||
std::vector<AuthQuery::Privilege> privileges,
|
||||
Symbol user_symbol, Symbol role_symbol, Symbol grants_symbol);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(database::GraphDbAccessor & db)
|
||||
const override;
|
||||
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
||||
virtual std::vector<Symbol> ModifiedSymbols(const SymbolTable &)
|
||||
const override { return {}; }
|
||||
|
||||
|
@ -185,10 +185,14 @@ class RuleBasedPlanner {
|
||||
} else if (auto *auth_query =
|
||||
dynamic_cast<query::AuthQuery *>(clause)) {
|
||||
DCHECK(!input_op) << "Unexpected operator before AuthQuery";
|
||||
auto &symbol_table = context.symbol_table;
|
||||
input_op = std::make_unique<plan::AuthHandler>(
|
||||
auth_query->action_, auth_query->user_, auth_query->role_,
|
||||
auth_query->user_or_role_, auth_query->password_,
|
||||
auth_query->privileges_);
|
||||
auth_query->privileges_,
|
||||
symbol_table.CreateSymbol("user", false),
|
||||
symbol_table.CreateSymbol("role", false),
|
||||
symbol_table.CreateSymbol("grants", false));
|
||||
} else if (auto *create_stream =
|
||||
dynamic_cast<query::CreateStream *>(clause)) {
|
||||
DCHECK(!input_op) << "Unexpected operator before CreateStream";
|
||||
|
@ -40,6 +40,15 @@ bool KVStore::Put(const std::string &key, const std::string &value) {
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
|
||||
rocksdb::WriteBatch batch;
|
||||
for (const auto &item : items) {
|
||||
batch.Put(item.first, item.second);
|
||||
}
|
||||
auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
std::experimental::optional<std::string> KVStore::Get(
|
||||
const std::string &key) const noexcept {
|
||||
std::string value;
|
||||
@ -53,6 +62,15 @@ bool KVStore::Delete(const std::string &key) {
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
bool KVStore::DeleteMultiple(const std::vector<std::string> &keys) {
|
||||
rocksdb::WriteBatch batch;
|
||||
for (const auto &key : keys) {
|
||||
batch.Delete(key);
|
||||
}
|
||||
auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
bool KVStore::DeletePrefix(const std::string &prefix) {
|
||||
std::unique_ptr<rocksdb::Iterator> iter = std::unique_ptr<rocksdb::Iterator>(
|
||||
pimpl_->db->NewIterator(rocksdb::ReadOptions()));
|
||||
@ -64,6 +82,20 @@ bool KVStore::DeletePrefix(const std::string &prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KVStore::PutAndDeleteMultiple(
|
||||
const std::map<std::string, std::string> &items,
|
||||
const std::vector<std::string> &keys) {
|
||||
rocksdb::WriteBatch batch;
|
||||
for (const auto &item : items) {
|
||||
batch.Put(item.first, item.second);
|
||||
}
|
||||
for (const auto &key : keys) {
|
||||
batch.Delete(key);
|
||||
}
|
||||
auto s = pimpl_->db->Write(rocksdb::WriteOptions(), &batch);
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
// iterator
|
||||
|
||||
struct KVStore::iterator::impl {
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <experimental/optional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
@ -49,6 +51,16 @@ class KVStore final {
|
||||
*/
|
||||
bool Put(const std::string &key, const std::string &value);
|
||||
|
||||
/**
|
||||
* Store values under the given keys.
|
||||
*
|
||||
* @param items
|
||||
*
|
||||
* @return true if the items have been successfully stored.
|
||||
* In case of any error false is going to be returned.
|
||||
*/
|
||||
bool PutMultiple(const std::map<std::string, std::string> &items);
|
||||
|
||||
/**
|
||||
* Retrieve value for the given key.
|
||||
*
|
||||
@ -71,6 +83,17 @@ class KVStore final {
|
||||
*/
|
||||
bool Delete(const std::string &key);
|
||||
|
||||
/**
|
||||
* Deletes the keys and corresponding values from storage.
|
||||
*
|
||||
* @param keys
|
||||
*
|
||||
* @return True on success, false on error. The return value is
|
||||
* true if the keys don't exist and underlying storage
|
||||
* didn't encounter any error.
|
||||
*/
|
||||
bool DeleteMultiple(const std::vector<std::string> &keys);
|
||||
|
||||
/**
|
||||
* Delete all (key, value) pairs where key begins with a given prefix.
|
||||
*
|
||||
@ -83,6 +106,18 @@ class KVStore final {
|
||||
*/
|
||||
bool DeletePrefix(const std::string &prefix = "");
|
||||
|
||||
/**
|
||||
* Store values under the given keys and delete the keys.
|
||||
*
|
||||
* @param items
|
||||
* @param keys
|
||||
*
|
||||
* @return true if the items have been successfully stored and deleted.
|
||||
* In case of any error false is going to be returned.
|
||||
*/
|
||||
bool PutAndDeleteMultiple(const std::map<std::string, std::string> &items,
|
||||
const std::vector<std::string> &keys);
|
||||
|
||||
/**
|
||||
* Returns total number of stored (key, value) pairs. The function takes an
|
||||
* optional prefix parameter used for filtering keys that start with that
|
||||
|
@ -16,6 +16,11 @@ bool KVStore::Put(const std::string &key, const std::string &value) {
|
||||
<< "Unsupported operation (KVStore::Put) -- this is a dummy kvstore";
|
||||
}
|
||||
|
||||
bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
|
||||
CHECK(false) << "Unsupported operation (KVStore::PutMultiple) -- this is a "
|
||||
"dummy kvstore";
|
||||
}
|
||||
|
||||
std::experimental::optional<std::string> KVStore::Get(
|
||||
const std::string &key) const noexcept {
|
||||
CHECK(false)
|
||||
@ -27,11 +32,24 @@ bool KVStore::Delete(const std::string &key) {
|
||||
<< "Unsupported operation (KVStore::Delete) -- this is a dummy kvstore";
|
||||
}
|
||||
|
||||
bool KVStore::DeleteMultiple(const std::vector<std::string> &keys) {
|
||||
CHECK(false) << "Unsupported operation (KVStore::DeleteMultiple) -- this is "
|
||||
"a dummy kvstore";
|
||||
}
|
||||
|
||||
bool KVStore::DeletePrefix(const std::string &prefix) {
|
||||
CHECK(false) << "Unsupported operation (KVStore::DeletePrefix) -- this is a "
|
||||
"dummy kvstore";
|
||||
}
|
||||
|
||||
bool KVStore::PutAndDeleteMultiple(
|
||||
const std::map<std::string, std::string> &items,
|
||||
const std::vector<std::string> &keys) {
|
||||
CHECK(false)
|
||||
<< "Unsupported operation (KVStore::PutAndDeleteMultiple) -- this is a "
|
||||
"dummy kvstore";
|
||||
}
|
||||
|
||||
// iterator
|
||||
|
||||
struct KVStore::iterator::impl {};
|
||||
|
@ -12,9 +12,16 @@
|
||||
using namespace auth;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
DECLARE_bool(auth_password_permit_null);
|
||||
DECLARE_string(auth_password_strength_regex);
|
||||
|
||||
class AuthWithStorage : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() { utils::EnsureDir(test_folder_); }
|
||||
virtual void SetUp() {
|
||||
utils::EnsureDir(test_folder_);
|
||||
FLAGS_auth_password_permit_null = true;
|
||||
FLAGS_auth_password_strength_regex = ".+";
|
||||
}
|
||||
|
||||
virtual void TearDown() { fs::remove_all(test_folder_); }
|
||||
|
||||
@ -64,15 +71,24 @@ TEST_F(AuthWithStorage, Authenticate) {
|
||||
ASSERT_NE(user, std::experimental::nullopt);
|
||||
ASSERT_TRUE(auth.HasUsers());
|
||||
|
||||
ASSERT_THROW(auth.Authenticate("test", "123"), utils::BasicException);
|
||||
ASSERT_TRUE(auth.Authenticate("test", "123"));
|
||||
|
||||
user->UpdatePassword("123");
|
||||
ASSERT_TRUE(auth.SaveUser(*user));
|
||||
auth.SaveUser(*user);
|
||||
|
||||
ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
|
||||
|
||||
ASSERT_EQ(auth.Authenticate("test", "456"), std::experimental::nullopt);
|
||||
ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
|
||||
|
||||
user->UpdatePassword();
|
||||
auth.SaveUser(*user);
|
||||
|
||||
ASSERT_NE(auth.Authenticate("test", "123"), std::experimental::nullopt);
|
||||
ASSERT_NE(auth.Authenticate("test", "456"), std::experimental::nullopt);
|
||||
|
||||
ASSERT_EQ(auth.Authenticate("nonexistant", "123"),
|
||||
std::experimental::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserRolePermissions) {
|
||||
@ -84,27 +100,27 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
|
||||
ASSERT_NE(user, std::experimental::nullopt);
|
||||
|
||||
// Test initial user permissions.
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Read),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Create),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Update),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Delete),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::MATCH),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::CREATE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::MERGE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::DELETE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions(), user->GetPermissions());
|
||||
|
||||
// Change one user permission.
|
||||
user->permissions().Grant(Permission::Read);
|
||||
user->permissions().Grant(Permission::MATCH);
|
||||
|
||||
// Check permissions.
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Create),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Update),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::Delete),
|
||||
PermissionLevel::Neutral);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::CREATE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::MERGE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions().Has(Permission::DELETE),
|
||||
PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(user->permissions(), user->GetPermissions());
|
||||
|
||||
// Create role.
|
||||
@ -113,29 +129,29 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
|
||||
ASSERT_NE(role, std::experimental::nullopt);
|
||||
|
||||
// Assign permissions to role and role to user.
|
||||
role->permissions().Grant(Permission::Delete);
|
||||
role->permissions().Grant(Permission::DELETE);
|
||||
user->SetRole(*role);
|
||||
|
||||
// Check permissions.
|
||||
{
|
||||
auto permissions = user->GetPermissions();
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
|
||||
}
|
||||
|
||||
// Add explicit deny to role.
|
||||
role->permissions().Deny(Permission::Read);
|
||||
role->permissions().Deny(Permission::MATCH);
|
||||
user->SetRole(*role);
|
||||
|
||||
// Check permissions.
|
||||
{
|
||||
auto permissions = user->GetPermissions();
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Deny);
|
||||
ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::DENY);
|
||||
ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,6 +218,126 @@ TEST_F(AuthWithStorage, RoleManipulations) {
|
||||
ASSERT_TRUE(role2);
|
||||
ASSERT_EQ(role2->rolename(), "role2");
|
||||
}
|
||||
|
||||
{
|
||||
auto users = auth.AllUsers();
|
||||
std::sort(users.begin(), users.end(), [](const User &a, const User &b) {
|
||||
return a.username() < b.username();
|
||||
});
|
||||
ASSERT_EQ(users.size(), 2);
|
||||
ASSERT_EQ(users[0].username(), "user1");
|
||||
ASSERT_EQ(users[1].username(), "user2");
|
||||
}
|
||||
|
||||
{
|
||||
auto roles = auth.AllRoles();
|
||||
std::sort(roles.begin(), roles.end(), [](const Role &a, const Role &b) {
|
||||
return a.rolename() < b.rolename();
|
||||
});
|
||||
ASSERT_EQ(roles.size(), 2);
|
||||
ASSERT_EQ(roles[0].rolename(), "role1");
|
||||
ASSERT_EQ(roles[1].rolename(), "role2");
|
||||
}
|
||||
|
||||
{
|
||||
auto users = auth.AllUsersForRole("role2");
|
||||
ASSERT_EQ(users.size(), 1);
|
||||
ASSERT_EQ(users[0].username(), "user2");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserRoleLinkUnlink) {
|
||||
{
|
||||
auto user = auth.AddUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
auto role = auth.AddRole("role");
|
||||
ASSERT_TRUE(role);
|
||||
user->SetRole(*role);
|
||||
auth.SaveUser(*user);
|
||||
}
|
||||
|
||||
{
|
||||
auto user = auth.GetUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
auto role = user->role();
|
||||
ASSERT_TRUE(role);
|
||||
ASSERT_EQ(role->rolename(), "role");
|
||||
}
|
||||
|
||||
{
|
||||
auto user = auth.GetUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
user->ClearRole();
|
||||
auth.SaveUser(*user);
|
||||
}
|
||||
|
||||
{
|
||||
auto user = auth.GetUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
ASSERT_FALSE(user->role());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserPasswordCreation) {
|
||||
{
|
||||
auto user = auth.AddUser("test");
|
||||
ASSERT_TRUE(user);
|
||||
ASSERT_TRUE(auth.Authenticate("test", "123"));
|
||||
ASSERT_TRUE(auth.Authenticate("test", "456"));
|
||||
ASSERT_TRUE(auth.RemoveUser(user->username()));
|
||||
}
|
||||
|
||||
{
|
||||
auto user = auth.AddUser("test", "123");
|
||||
ASSERT_TRUE(user);
|
||||
ASSERT_TRUE(auth.Authenticate("test", "123"));
|
||||
ASSERT_FALSE(auth.Authenticate("test", "456"));
|
||||
ASSERT_TRUE(auth.RemoveUser(user->username()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, PasswordStrength) {
|
||||
const std::string kWeakRegex = ".+";
|
||||
// https://stackoverflow.com/questions/5142103/regex-to-validate-password-strength
|
||||
const std::string kStrongRegex =
|
||||
"^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*["
|
||||
"a-z]).{8,}$";
|
||||
|
||||
const std::string kWeakPassword = "weak";
|
||||
const std::string kAlmostStrongPassword =
|
||||
"ThisPasswordMeetsAllButOneCriterion1234";
|
||||
const std::string kStrongPassword = "ThisIsAVeryStrongPassword123$";
|
||||
|
||||
auto user = auth.AddUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
|
||||
FLAGS_auth_password_permit_null = true;
|
||||
FLAGS_auth_password_strength_regex = kWeakRegex;
|
||||
ASSERT_NO_THROW(user->UpdatePassword());
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
|
||||
|
||||
FLAGS_auth_password_permit_null = false;
|
||||
FLAGS_auth_password_strength_regex = kWeakRegex;
|
||||
ASSERT_THROW(user->UpdatePassword(), AuthException);
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword));
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword));
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
|
||||
|
||||
FLAGS_auth_password_permit_null = true;
|
||||
FLAGS_auth_password_strength_regex = kStrongRegex;
|
||||
ASSERT_NO_THROW(user->UpdatePassword());
|
||||
ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
|
||||
ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
|
||||
|
||||
FLAGS_auth_password_permit_null = false;
|
||||
FLAGS_auth_password_strength_regex = kStrongRegex;
|
||||
ASSERT_THROW(user->UpdatePassword(), AuthException);
|
||||
ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException);
|
||||
ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException);
|
||||
ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword));
|
||||
}
|
||||
|
||||
TEST(AuthWithoutStorage, Permissions) {
|
||||
@ -209,50 +345,50 @@ TEST(AuthWithoutStorage, Permissions) {
|
||||
ASSERT_EQ(permissions.grants(), 0);
|
||||
ASSERT_EQ(permissions.denies(), 0);
|
||||
|
||||
permissions.Grant(Permission::Read);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
|
||||
permissions.Grant(Permission::MATCH);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
|
||||
ASSERT_EQ(permissions.denies(), 0);
|
||||
|
||||
permissions.Revoke(Permission::Read);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Neutral);
|
||||
permissions.Revoke(Permission::MATCH);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.grants(), 0);
|
||||
ASSERT_EQ(permissions.denies(), 0);
|
||||
|
||||
permissions.Deny(Permission::Read);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Deny);
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Read));
|
||||
permissions.Deny(Permission::MATCH);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::DENY);
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::MATCH));
|
||||
ASSERT_EQ(permissions.grants(), 0);
|
||||
|
||||
permissions.Grant(Permission::Read);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
|
||||
permissions.Grant(Permission::MATCH);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
|
||||
ASSERT_EQ(permissions.denies(), 0);
|
||||
|
||||
permissions.Deny(Permission::Create);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
|
||||
ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
|
||||
permissions.Deny(Permission::CREATE);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
|
||||
ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
|
||||
|
||||
permissions.Grant(Permission::Delete);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
|
||||
ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Grant);
|
||||
permissions.Grant(Permission::DELETE);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
|
||||
ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.grants(),
|
||||
utils::UnderlyingCast(Permission::Read) |
|
||||
utils::UnderlyingCast(Permission::Delete));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
|
||||
utils::UnderlyingCast(Permission::MATCH) |
|
||||
utils::UnderlyingCast(Permission::DELETE));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
|
||||
|
||||
permissions.Revoke(Permission::Delete);
|
||||
ASSERT_EQ(permissions.Has(Permission::Read), PermissionLevel::Grant);
|
||||
ASSERT_EQ(permissions.Has(Permission::Create), PermissionLevel::Deny);
|
||||
ASSERT_EQ(permissions.Has(Permission::Update), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.Has(Permission::Delete), PermissionLevel::Neutral);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::Read));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::Create));
|
||||
permissions.Revoke(Permission::DELETE);
|
||||
ASSERT_EQ(permissions.Has(Permission::MATCH), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(permissions.Has(Permission::CREATE), PermissionLevel::DENY);
|
||||
ASSERT_EQ(permissions.Has(Permission::MERGE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.Has(Permission::DELETE), PermissionLevel::NEUTRAL);
|
||||
ASSERT_EQ(permissions.grants(), utils::UnderlyingCast(Permission::MATCH));
|
||||
ASSERT_EQ(permissions.denies(), utils::UnderlyingCast(Permission::CREATE));
|
||||
}
|
||||
|
||||
TEST(AuthWithoutStorage, PermissionsMaskTest) {
|
||||
@ -275,8 +411,8 @@ TEST(AuthWithoutStorage, PermissionsMaskTest) {
|
||||
|
||||
TEST(AuthWithoutStorage, UserSerializeDeserialize) {
|
||||
auto user = User("test");
|
||||
user.permissions().Grant(Permission::Read);
|
||||
user.permissions().Deny(Permission::Update);
|
||||
user.permissions().Grant(Permission::MATCH);
|
||||
user.permissions().Deny(Permission::MERGE);
|
||||
user.UpdatePassword("world");
|
||||
|
||||
auto data = user.Serialize();
|
||||
@ -287,8 +423,8 @@ TEST(AuthWithoutStorage, UserSerializeDeserialize) {
|
||||
|
||||
TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
|
||||
auto role = Role("test");
|
||||
role.permissions().Grant(Permission::Read);
|
||||
role.permissions().Deny(Permission::Update);
|
||||
role.permissions().Grant(Permission::MATCH);
|
||||
role.permissions().Deny(Permission::MERGE);
|
||||
|
||||
auto data = role.Serialize();
|
||||
|
||||
@ -297,25 +433,32 @@ TEST(AuthWithoutStorage, RoleSerializeDeserialize) {
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserWithRoleSerializeDeserialize) {
|
||||
auto role = auth.AddRole("test");
|
||||
auto role = auth.AddRole("role");
|
||||
ASSERT_TRUE(role);
|
||||
role->permissions().Grant(Permission::Read);
|
||||
role->permissions().Deny(Permission::Update);
|
||||
role->permissions().Grant(Permission::MATCH);
|
||||
role->permissions().Deny(Permission::MERGE);
|
||||
auth.SaveRole(*role);
|
||||
|
||||
auto user = auth.AddUser("test");
|
||||
auto user = auth.AddUser("user");
|
||||
ASSERT_TRUE(user);
|
||||
user->permissions().Grant(Permission::Read);
|
||||
user->permissions().Deny(Permission::Update);
|
||||
user->permissions().Grant(Permission::MATCH);
|
||||
user->permissions().Deny(Permission::MERGE);
|
||||
user->UpdatePassword("world");
|
||||
user->SetRole(*role);
|
||||
auth.SaveUser(*user);
|
||||
|
||||
auto new_user = auth.GetUser("test");
|
||||
auto new_user = auth.GetUser("user");
|
||||
ASSERT_TRUE(new_user);
|
||||
ASSERT_EQ(*user, *new_user);
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserRoleUniqueName) {
|
||||
ASSERT_TRUE(auth.AddUser("user"));
|
||||
ASSERT_TRUE(auth.AddRole("role"));
|
||||
ASSERT_FALSE(auth.AddRole("user"));
|
||||
ASSERT_FALSE(auth.AddUser("role"));
|
||||
}
|
||||
|
||||
TEST(AuthWithoutStorage, Crypto) {
|
||||
auto hash = EncryptPassword("hello");
|
||||
ASSERT_TRUE(VerifyPassword("hello", hash));
|
||||
|
@ -2037,7 +2037,8 @@ TYPED_TEST(CypherMainVisitorTest, RevokePrivilege) {
|
||||
"", "", "user", {},
|
||||
{AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE,
|
||||
AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::SET, AuthQuery::Privilege::REMOVE,
|
||||
AuthQuery::Privilege::INDEX, AuthQuery::Privilege::AUTH,
|
||||
AuthQuery::Privilege::STREAM});
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,13 @@ TEST_F(KVStore, PutGet) {
|
||||
ASSERT_EQ(kvstore.Get("key").value(), "value");
|
||||
}
|
||||
|
||||
TEST_F(KVStore, PutMultipleGet) {
|
||||
storage::KVStore kvstore(test_folder_ / "PutMultipleGet");
|
||||
ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
|
||||
ASSERT_EQ(kvstore.Get("key1").value(), "value1");
|
||||
ASSERT_EQ(kvstore.Get("key2").value(), "value2");
|
||||
}
|
||||
|
||||
TEST_F(KVStore, PutGetDeleteGet) {
|
||||
storage::KVStore kvstore(test_folder_ / "PutGetDeleteGet");
|
||||
ASSERT_TRUE(kvstore.Put("key", "value"));
|
||||
@ -34,6 +41,28 @@ TEST_F(KVStore, PutGetDeleteGet) {
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key")));
|
||||
}
|
||||
|
||||
TEST_F(KVStore, PutMultipleGetDeleteMultipleGet) {
|
||||
storage::KVStore kvstore(test_folder_ / "PutMultipleGetDeleteMultipleGet");
|
||||
ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
|
||||
ASSERT_EQ(kvstore.Get("key1").value(), "value1");
|
||||
ASSERT_EQ(kvstore.Get("key2").value(), "value2");
|
||||
ASSERT_TRUE(kvstore.DeleteMultiple({"key1", "key2", "key3"}));
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key1")));
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key2")));
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key3")));
|
||||
}
|
||||
|
||||
TEST_F(KVStore, PutMultipleGetPutAndDeleteMultipleGet) {
|
||||
storage::KVStore kvstore(test_folder_ / "PutMultipleGetPutAndDeleteMultipleGet");
|
||||
ASSERT_TRUE(kvstore.PutMultiple({{"key1", "value1"}, {"key2", "value2"}}));
|
||||
ASSERT_EQ(kvstore.Get("key1").value(), "value1");
|
||||
ASSERT_EQ(kvstore.Get("key2").value(), "value2");
|
||||
ASSERT_TRUE(kvstore.PutAndDeleteMultiple({{"key3", "value3"}}, {"key1", "key2"}));
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key1")));
|
||||
ASSERT_FALSE(static_cast<bool>(kvstore.Get("key2")));
|
||||
ASSERT_EQ(kvstore.Get("key3").value(), "value3");
|
||||
}
|
||||
|
||||
TEST_F(KVStore, Durability) {
|
||||
{
|
||||
storage::KVStore kvstore(test_folder_ / "Durability");
|
||||
|
Loading…
Reference in New Issue
Block a user