Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[E129-MG < T1003-MG] Expand fine grained access checker with more granular permissions #496

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
206 changes: 96 additions & 110 deletions src/auth/models.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@

#include "auth/models.hpp"

#include <algorithm>
#include <iterator>
#include <cstdint>
#include <regex>
#include <unordered_set>

#include <gflags/gflags.h>

#include "auth/crypto.hpp"
#include "auth/exceptions.hpp"
#include "utils/cast.hpp"
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/settings.hpp"
#include "utils/string.hpp"

Expand Down Expand Up @@ -101,6 +100,24 @@ std::string PermissionLevelToString(PermissionLevel level) {
}
}

FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
const FineGrainedAccessPermissions &second) {
std::unordered_map<std::string, uint64_t> permissions{first.GetPermissions()};
std::optional<uint64_t> global_permission;

if (second.GetGlobalPermission().has_value()) {
global_permission = second.GetGlobalPermission().value();
} else if (first.GetGlobalPermission().has_value()) {
global_permission = first.GetGlobalPermission().value();
}

for (const auto &[label_name, permission] : second.GetPermissions()) {
permissions[label_name] = permission;
}

return FineGrainedAccessPermissions(permissions, global_permission);
}

Permissions::Permissions(uint64_t grants, uint64_t denies) {
// The deny bitmask has higher priority than the grant bitmask.
denies_ = denies;
Expand Down Expand Up @@ -186,112 +203,111 @@ bool operator==(const Permissions &first, const Permissions &second) {

bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }

const std::string ASTERISK = "*";
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions,
const std::optional<uint64_t> &global_permission)
: permissions_(permissions), global_permission_(global_permission) {}

PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission,
const LabelPermission label_permission) const {
const auto concrete_permission = std::invoke([&]() -> uint64_t {
if (permissions_.contains(permission)) {
return permissions_.at(permission);
jbajic marked this conversation as resolved.
Show resolved Hide resolved
}

FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants,
const std::unordered_set<std::string> &denies)
: grants_(grants), denies_(denies) {}
if (global_permission_.has_value()) {
return global_permission_.value();
}

PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission) const {
if ((denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) || denies_.find(permission) != denies_.end()) {
return PermissionLevel::DENY;
}
return 0;
});

if ((grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) || grants_.find(permission) != denies_.end()) {
return PermissionLevel::GRANT;
}
const auto temp_permission = concrete_permission & label_permission;

return PermissionLevel::NEUTRAL;
return temp_permission > 0 ? PermissionLevel::GRANT : PermissionLevel::DENY;
}

void FineGrainedAccessPermissions::Grant(const std::string &permission) {
void FineGrainedAccessPermissions::Grant(const std::string &permission, const LabelPermission label_permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
grants_.insert(permission);

return;
}

auto deniedPermissionIter = denies_.find(permission);

if (deniedPermissionIter != denies_.end()) {
denies_.erase(deniedPermissionIter);
global_permission_ = CalculateGrant(label_permission);
} else {
permissions_[permission] |= CalculateGrant(label_permission);
}
}

if (grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) {
grants_.erase(ASTERISK);
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
if (permission == ASTERISK) {
permissions_.clear();
global_permission_ = std::nullopt;
} else {
permissions_.erase(permission);
}
}

if (grants_.find(permission) == grants_.end()) {
grants_.insert(permission);
void FineGrainedAccessPermissions::Deny(const std::string &permission, const LabelPermission label_permission) {
if (permission == ASTERISK) {
global_permission_ = CalculateDeny(label_permission);
} else {
permissions_[permission] = CalculateDeny(label_permission);
}
}

void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["permissions"] = permissions_;
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
return data;
}

return;
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load permissions data!");
}

auto deniedPermissionIter = denies_.find(permission);
auto grantedPermissionIter = grants_.find(permission);
std::optional<uint64_t> global_permission;

if (deniedPermissionIter != denies_.end()) {
denies_.erase(deniedPermissionIter);
if (data["global_permission"].empty() || data["global_permission"] == -1) {
global_permission = std::nullopt;
} else {
global_permission = data["global_permission"];
}

if (grantedPermissionIter != grants_.end()) {
grants_.erase(grantedPermissionIter);
}
return FineGrainedAccessPermissions(data["permissions"], global_permission);
}

void FineGrainedAccessPermissions::Deny(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
denies_.insert(permission);

return;
}

auto grantedPermissionIter = grants_.find(permission);
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
return permissions_;
}
const std::optional<uint64_t> &FineGrainedAccessPermissions::GetGlobalPermission() const { return global_permission_; };

if (grantedPermissionIter != grants_.end()) {
grants_.erase(grantedPermissionIter);
}
uint64_t FineGrainedAccessPermissions::CalculateGrant(LabelPermission label_permission) {
uint64_t shift{1};
uint64_t result{0};
auto uint_label_permission = static_cast<uint64_t>(label_permission);

if (denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) {
denies_.erase(ASTERISK);
while (uint_label_permission > 0) {
result |= uint_label_permission;
uint_label_permission >>= shift;
}

if (denies_.find(permission) == denies_.end()) {
denies_.insert(permission);
}
return result;
}

nlohmann::json FineGrainedAccessPermissions::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["grants"] = grants_;
data["denies"] = denies_;
return data;
}
uint64_t FineGrainedAccessPermissions::CalculateDeny(LabelPermission label_permission) {
uint64_t shift{1};
uint64_t result{0};
auto uint_label_permission = static_cast<uint64_t>(label_permission);

FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load permissions data!");
while (uint_label_permission <= LabelPermissionMax) {
result |= uint_label_permission;
uint_label_permission <<= shift;
}

return FineGrainedAccessPermissions(data["grants"], data["denies"]);
return LabelPermissionAll - result;
}

const std::unordered_set<std::string> &FineGrainedAccessPermissions::grants() const { return grants_; }
const std::unordered_set<std::string> &FineGrainedAccessPermissions::denies() const { return denies_; }

bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return first.grants() == second.grants() && first.denies() == second.denies();
return first.GetPermissions() == second.GetPermissions() &&
first.GetGlobalPermission() == second.GetGlobalPermission();
}

bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
Expand Down Expand Up @@ -321,7 +337,7 @@ FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::j
if (!data.is_object()) {
throw AuthException("Couldn't load role data!");
}
if (!data["label_permissions"].is_object() && !data["edge_type_permissions"].is_object()) {
if (!data["label_permissions"].is_object() || !data["edge_type_permissions"].is_object()) {
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
}
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
Expand Down Expand Up @@ -439,46 +455,16 @@ Permissions User::GetPermissions() const {

FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;

std::set_union(fine_grained_access_handler_.label_permissions().grants().begin(),
fine_grained_access_handler_.label_permissions().grants().end(),
role_->fine_grained_access_handler().label_permissions().grants().begin(),
role_->fine_grained_access_handler().label_permissions().grants().end(),
std::inserter(resultGrants, resultGrants.begin()));

std::unordered_set<std::string> resultDenies;

std::set_union(fine_grained_access_handler_.label_permissions().denies().begin(),
fine_grained_access_handler_.label_permissions().denies().end(),
role_->fine_grained_access_handler().label_permissions().denies().begin(),
role_->fine_grained_access_handler().label_permissions().denies().end(),
std::inserter(resultDenies, resultDenies.begin()));

return FineGrainedAccessPermissions(resultGrants, resultDenies);
return Merge(role()->fine_grained_access_handler().label_permissions(),
fine_grained_access_handler_.label_permissions());
}
return fine_grained_access_handler_.label_permissions();
}

FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;

std::set_union(fine_grained_access_handler_.edge_type_permissions().grants().begin(),
fine_grained_access_handler_.edge_type_permissions().grants().end(),
role_->fine_grained_access_handler().edge_type_permissions().grants().begin(),
role_->fine_grained_access_handler().edge_type_permissions().grants().end(),
std::inserter(resultGrants, resultGrants.begin()));

std::unordered_set<std::string> resultDenies;

std::set_union(fine_grained_access_handler_.edge_type_permissions().denies().begin(),
fine_grained_access_handler_.edge_type_permissions().denies().end(),
role_->fine_grained_access_handler().edge_type_permissions().denies().begin(),
role_->fine_grained_access_handler().edge_type_permissions().denies().end(),
std::inserter(resultDenies, resultDenies.begin()));

return FineGrainedAccessPermissions(resultGrants, resultDenies);
return Merge(role()->fine_grained_access_handler().edge_type_permissions(),
fine_grained_access_handler_.edge_type_permissions());
}
return fine_grained_access_handler_.edge_type_permissions();
}
Expand Down
58 changes: 41 additions & 17 deletions src/auth/models.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

#include <optional>
#include <string>
#include <unordered_set>
#include <unordered_map>

#include <json/json.hpp>
#include <unordered_set>

namespace memgraph::auth {
const std::string ASTERISK = "*";
// These permissions must have values that are applicable for usage in a
// bitmask.
// clang-format off
Expand Down Expand Up @@ -44,15 +44,34 @@ enum class Permission : uint64_t {
};
// clang-format on

// clang-format off
enum class LabelPermission : uint64_t {
READ = 1,
EDIT = 1U << 1U,
jbajic marked this conversation as resolved.
Show resolved Hide resolved
CREATE_DELETE = 1U << 2U
};
// clang-format on

constexpr inline uint64_t operator|(LabelPermission lhs, LabelPermission rhs) {
return static_cast<uint64_t>(lhs) | static_cast<uint64_t>(rhs);
}

constexpr inline uint64_t operator|(uint64_t lhs, LabelPermission rhs) { return lhs | static_cast<uint64_t>(rhs); }

constexpr inline uint64_t operator&(uint64_t lhs, LabelPermission rhs) {
return (lhs & static_cast<uint64_t>(rhs)) != 0;
}

constexpr uint64_t LabelPermissionAll = memgraph::auth::LabelPermission::CREATE_DELETE |
memgraph::auth::LabelPermission::EDIT | memgraph::auth::LabelPermission::READ;
constexpr uint64_t LabelPermissionMax = static_cast<uint64_t>(memgraph::auth::LabelPermission::CREATE_DELETE);
constexpr uint64_t LabelPermissionMin = static_cast<uint64_t>(memgraph::auth::LabelPermission::READ);

// 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,
};
enum class PermissionLevel : short { GRANT, NEUTRAL, DENY };
jbajic marked this conversation as resolved.
Show resolved Hide resolved

// Function that converts a permission level to its string representation.
std::string PermissionLevelToString(PermissionLevel level);
Expand Down Expand Up @@ -98,34 +117,36 @@ bool operator!=(const Permissions &first, const Permissions &second);

class FineGrainedAccessPermissions final {
public:
explicit FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants = {},
const std::unordered_set<std::string> &denies = {});

explicit FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions = {},
const std::optional<uint64_t> &global_permission = std::nullopt);
FineGrainedAccessPermissions(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions &operator=(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions(FineGrainedAccessPermissions &&) = default;
FineGrainedAccessPermissions &operator=(FineGrainedAccessPermissions &&) = default;
~FineGrainedAccessPermissions() = default;

PermissionLevel Has(const std::string &permission) const;
PermissionLevel Has(const std::string &permission, LabelPermission label_permission) const;

void Grant(const std::string &permission);
void Grant(const std::string &permission, LabelPermission label_permission);

void Revoke(const std::string &permission);

void Deny(const std::string &permission);
void Deny(const std::string &permission, LabelPermission label_permission);

nlohmann::json Serialize() const;

/// @throw AuthException if unable to deserialize.
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);

const std::unordered_set<std::string> &grants() const;
const std::unordered_set<std::string> &denies() const;
const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
const std::optional<uint64_t> &GetGlobalPermission() const;

private:
std::unordered_set<std::string> grants_{};
std::unordered_set<std::string> denies_{};
std::unordered_map<std::string, uint64_t> permissions_{};
std::optional<uint64_t> global_permission_;

static uint64_t CalculateGrant(LabelPermission label_permission);
static uint64_t CalculateDeny(LabelPermission label_permission);
};

bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
Expand Down Expand Up @@ -252,4 +273,7 @@ class User final {
};

bool operator==(const User &first, const User &second);

FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
const FineGrainedAccessPermissions &second);
} // namespace memgraph::auth