Implement leftover Auth queries

Reviewers: mtomic, buda

Reviewed By: mtomic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1535
This commit is contained in:
Matej Ferencevic 2018-08-14 11:34:00 +02:00
parent 1febc15d68
commit 94ad18326c
20 changed files with 1045 additions and 195 deletions

View File

@ -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_; }

View File

@ -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:

View File

@ -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
View 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

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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!";

View File

@ -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 )* ;

View File

@ -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(

View File

@ -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 {}; }

View File

@ -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";

View File

@ -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 {

View File

@ -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

View File

@ -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 {};

View File

@ -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));

View File

@ -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});
}

View File

@ -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");