diff --git a/src/auth/models.cpp b/src/auth/models.cpp index 54bf24da6..8e275e550 100644 --- a/src/auth/models.cpp +++ b/src/auth/models.cpp @@ -183,19 +183,137 @@ 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_set &grants, + const std::unordered_set &denies) + : grants_(grants), denies_(denies) {} + +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; + } + + if ((grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) || grants_.find(permission) != denies_.end()) { + return PermissionLevel::GRANT; + } + + return PermissionLevel::NEUTRAL; +} + +void FineGrainedAccessPermissions::Grant(const std::string &permission) { + if (permission == ASTERISK) { + grants_.clear(); + grants_.insert(permission); + + return; + } + + auto deniedPermissionIter = denies_.find(permission); + + if (deniedPermissionIter != denies_.end()) { + denies_.erase(deniedPermissionIter); + } + + if (grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) { + grants_.erase(ASTERISK); + } + + if (grants_.find(permission) == grants_.end()) { + grants_.insert(permission); + } +} + +void FineGrainedAccessPermissions::Revoke(const std::string &permission) { + if (permission == ASTERISK) { + grants_.clear(); + denies_.clear(); + + return; + } + + auto deniedPermissionIter = denies_.find(permission); + auto grantedPermissionIter = grants_.find(permission); + + if (deniedPermissionIter != denies_.end()) { + denies_.erase(deniedPermissionIter); + } + + if (grantedPermissionIter != grants_.end()) { + grants_.erase(grantedPermissionIter); + } +} + +void FineGrainedAccessPermissions::Deny(const std::string &permission) { + if (permission == ASTERISK) { + denies_.clear(); + denies_.insert(permission); + + return; + } + + auto grantedPermissionIter = grants_.find(permission); + + if (grantedPermissionIter != grants_.end()) { + grants_.erase(grantedPermissionIter); + } + + if (denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) { + denies_.erase(ASTERISK); + } + + if (denies_.find(permission) == denies_.end()) { + denies_.insert(permission); + } +} + +nlohmann::json FineGrainedAccessPermissions::Serialize() const { + nlohmann::json data = nlohmann::json::object(); + data["grants"] = grants_; + data["denies"] = denies_; + return data; +} + +FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) { + if (!data.is_object()) { + throw AuthException("Couldn't load permissions data!"); + } + + return {data["grants"], data["denies"]}; +} + +const std::unordered_set &FineGrainedAccessPermissions::grants() const { return grants_; } +const std::unordered_set &FineGrainedAccessPermissions::denies() const { return denies_; } + +bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) { + return first.grants() == second.grants() && first.denies() == second.denies(); +} + +bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) { + return !(first == second); +} + Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {} -Role::Role(const std::string &rolename, const Permissions &permissions) - : rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {} +Role::Role(const std::string &rolename, const Permissions &permissions, + const FineGrainedAccessPermissions &fine_grained_access_permissions) + : rolename_(utils::ToLowerCase(rolename)), + permissions_(permissions), + fine_grained_access_permissions_(fine_grained_access_permissions) {} const std::string &Role::rolename() const { return rolename_; } const Permissions &Role::permissions() const { return permissions_; } Permissions &Role::permissions() { return permissions_; } +const FineGrainedAccessPermissions &Role::fine_grained_access_permissions() const { + return fine_grained_access_permissions_; +} +FineGrainedAccessPermissions &Role::fine_grained_access_permissions() { return fine_grained_access_permissions_; } nlohmann::json Role::Serialize() const { nlohmann::json data = nlohmann::json::object(); data["rolename"] = rolename_; data["permissions"] = permissions_.Serialize(); + data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize(); return data; } @@ -203,11 +321,14 @@ Role Role::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load role data!"); } - if (!data["rolename"].is_string() || !data["permissions"].is_object()) { + if (!data["rolename"].is_string() || !data["permissions"].is_object() || + !data["fine_grained_access_permissions"].is_object()) { throw AuthException("Couldn't load role data!"); } auto permissions = Permissions::Deserialize(data["permissions"]); - return {data["rolename"], permissions}; + auto fine_grained_access_permissions = + FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]); + return {data["rolename"], permissions, fine_grained_access_permissions}; } bool operator==(const Role &first, const Role &second) { @@ -216,8 +337,12 @@ bool operator==(const Role &first, const Role &second) { User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {} -User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions) - : username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {} +User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions, + const FineGrainedAccessPermissions &fine_grained_access_permissions) + : username_(utils::ToLowerCase(username)), + password_hash_(password_hash), + permissions_(permissions), + fine_grained_access_permissions_(fine_grained_access_permissions) {} bool User::CheckPassword(const std::string &password) { if (password_hash_.empty()) return true; @@ -266,10 +391,35 @@ Permissions User::GetPermissions() const { return permissions_; } +FineGrainedAccessPermissions User::GetFineGrainedAccessPermissions() const { + if (role_) { + std::unordered_set resultGrants; + + std::set_union(fine_grained_access_permissions_.grants().begin(), fine_grained_access_permissions_.grants().end(), + role_->fine_grained_access_permissions().grants().begin(), + role_->fine_grained_access_permissions().grants().end(), + std::inserter(resultGrants, resultGrants.begin())); + + std::unordered_set resultDenies; + + std::set_union(fine_grained_access_permissions_.denies().begin(), fine_grained_access_permissions_.denies().end(), + role_->fine_grained_access_permissions().denies().begin(), + role_->fine_grained_access_permissions().denies().end(), + std::inserter(resultDenies, resultDenies.begin())); + + return {resultGrants, resultDenies}; + } + return fine_grained_access_permissions_; +} + const std::string &User::username() const { return username_; } const Permissions &User::permissions() const { return permissions_; } Permissions &User::permissions() { return permissions_; } +const FineGrainedAccessPermissions &User::fine_grained_access_permissions() const { + return fine_grained_access_permissions_; +} +FineGrainedAccessPermissions &User::fine_grained_access_permissions() { return fine_grained_access_permissions_; } const Role *User::role() const { if (role_.has_value()) { @@ -283,6 +433,7 @@ nlohmann::json User::Serialize() const { data["username"] = username_; data["password_hash"] = password_hash_; data["permissions"] = permissions_.Serialize(); + data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize(); // The role shouldn't be serialized here, it is stored as a foreign key. return data; } @@ -295,7 +446,9 @@ User User::Deserialize(const nlohmann::json &data) { throw AuthException("Couldn't load user data!"); } auto permissions = Permissions::Deserialize(data["permissions"]); - return {data["username"], data["password_hash"], permissions}; + auto fine_grained_access_permissions = + FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]); + return {data["username"], data["password_hash"], permissions, fine_grained_access_permissions}; } bool operator==(const User &first, const User &second) { diff --git a/src/auth/models.hpp b/src/auth/models.hpp index 0f01c0a39..0b721c8b9 100644 --- a/src/auth/models.hpp +++ b/src/auth/models.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -88,15 +89,48 @@ bool operator==(const Permissions &first, const Permissions &second); bool operator!=(const Permissions &first, const Permissions &second); +class FineGrainedAccessPermissions final { + public: + FineGrainedAccessPermissions(const std::unordered_set &grants = {}, + const std::unordered_set &denies = {}); + + PermissionLevel Has(const std::string &permission) const; + + void Grant(const std::string &permission); + + void Revoke(const std::string &permission); + + void Deny(const std::string &permission); + + nlohmann::json Serialize() const; + + /// @throw AuthException if unable to deserialize. + static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data); + + const std::unordered_set &grants() const; + const std::unordered_set &denies() const; + + private: + std::unordered_set grants_{}; + std::unordered_set denies_{}; +}; + +bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second); + +bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second); + class Role final { public: Role(const std::string &rolename); - Role(const std::string &rolename, const Permissions &permissions); + Role(const std::string &rolename, const Permissions &permissions, + const FineGrainedAccessPermissions &fine_grained_access_permissions); const std::string &rolename() const; const Permissions &permissions() const; Permissions &permissions(); + const FineGrainedAccessPermissions &fine_grained_access_permissions() const; + FineGrainedAccessPermissions &fine_grained_access_permissions(); nlohmann::json Serialize() const; @@ -108,6 +142,7 @@ class Role final { private: std::string rolename_; Permissions permissions_; + FineGrainedAccessPermissions fine_grained_access_permissions_; }; bool operator==(const Role &first, const Role &second); @@ -117,7 +152,8 @@ class User final { public: User(const std::string &username); - User(const std::string &username, const std::string &password_hash, const Permissions &permissions); + User(const std::string &username, const std::string &password_hash, const Permissions &permissions, + const FineGrainedAccessPermissions &fine_grained_access_permissions); /// @throw AuthException if unable to verify the password. bool CheckPassword(const std::string &password); @@ -130,11 +166,14 @@ class User final { void ClearRole(); Permissions GetPermissions() const; + FineGrainedAccessPermissions GetFineGrainedAccessPermissions() const; const std::string &username() const; const Permissions &permissions() const; Permissions &permissions(); + const FineGrainedAccessPermissions &fine_grained_access_permissions() const; + FineGrainedAccessPermissions &fine_grained_access_permissions(); const Role *role() const; @@ -149,6 +188,7 @@ class User final { std::string username_; std::string password_hash_; Permissions permissions_; + FineGrainedAccessPermissions fine_grained_access_permissions_; std::optional role_; }; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index 529789400..0cebcb897 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -255,7 +255,7 @@ privilege : CREATE | MODULE_READ | MODULE_WRITE | WEBSOCKET - | LABELS = labelList + | LABELS labels=labelList ; labelList : '*' | listOfLabels ;