diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 29c27eaf536434d2f870fc111ca00f5febec1327..a913c6906d61b4a171fe9955c9c2f81e17f60433 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -52,7 +52,9 @@ set(libcaosdb_INCL IF(BUILD_ACM) list(APPEND libcaosdb_INCL + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/permission_rule.h ) ENDIF() diff --git a/include/caosdb/acm/permission_rule.h b/include/caosdb/acm/permission_rule.h new file mode 100644 index 0000000000000000000000000000000000000000..017a33039b1dee350855f399187c871e01b074e7 --- /dev/null +++ b/include/caosdb/acm/permission_rule.h @@ -0,0 +1,69 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +/** + * @brief PermissionRules grant or deny permissions to roles. + * + * @file caosdb/acm/permission_rule.h + * @author Timm Fitchen + * @date 2022-02-11 + */ +#ifdef BUILD_ACM +#ifndef CAOSDB_ACM_PERMISSION_RULE_H +#define CAOSDB_ACM_PERMISSION_RULE_H + +#include <stddef.h> // for size_t +#include <unordered_set> // for unordered_set +#include <string> // for string + +namespace caosdb::acm { + +class PermissionRuleImpl; + +class PermissionRule { +public: + PermissionRule(std::string permission, bool isGrant, bool isPriority); + explicit PermissionRule(PermissionRuleImpl *wrapped); + PermissionRule(const PermissionRule &rule); + auto operator=(const PermissionRule &rule) -> PermissionRule &; + ~PermissionRule(); + auto ToString() const -> std::string; + [[nodiscard]] auto IsGrant() const -> bool; + auto SetGrant(bool isGrant) -> void; + [[nodiscard]] auto IsPriority() const -> bool; + auto SetPriority(bool isPriority) -> void; + [[nodiscard]] auto GetPermission() const -> const std::string &; + + auto operator==(const PermissionRule &other) const -> bool; + +private: + PermissionRuleImpl *impl; +}; + +struct HashPermissionRule { + auto operator()(const PermissionRule &rule) const -> size_t; +}; + +using PermissionRules = std::unordered_set<PermissionRule, HashPermissionRule>; + +} // namespace caosdb::acm +#endif +#endif diff --git a/include/caosdb/acm/role.h b/include/caosdb/acm/role.h new file mode 100644 index 0000000000000000000000000000000000000000..560449b12762206680d9445cbc7bbe6942d5b896 --- /dev/null +++ b/include/caosdb/acm/role.h @@ -0,0 +1,77 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +/** + * @brief Roles, together with users, and permissions are a fundamental concept + * of the access controll management of CaosDB. + * + * @file caosdb/acm/role.h + * @author Timm Fitchen + * @date 2022-02-11 + */ +#ifdef BUILD_ACM +#ifndef CAOSDB_ACM_ROLES_H +#define CAOSDB_ACM_ROLES_H + +#include "caosdb/acm/permission_rule.h" // for PermissionRule +#include <memory> // for unique_ptr +#include <string> // for string + +namespace caosdb::connection { +class Connection; +} + +namespace caosdb::acm { + +class RoleImpl; + +class Role { +public: + explicit Role(const std::string &name); + explicit Role(std::unique_ptr<RoleImpl> wrapped); + Role(const std::string &name, const std::string &description); + Role(const Role &role); + auto operator=(const Role &role) -> Role &; + ~Role(); + auto ToString() const -> std::string; + [[nodiscard]] auto GetName() const -> const std::string &; + auto SetName(const std::string &name) -> void; + [[nodiscard]] auto GetDescription() const -> const std::string &; + auto SetDescription(const std::string &description) -> void; + [[nodiscard]] auto GetPermissionRules() const -> const PermissionRules &; + // auto SetPermissionRules(PermissionRules rules) -> void; + auto ClearPermissionRules() -> void; + auto Grant(const std::string &permission, bool isPriority) -> void; + auto Deny(const std::string &permission, bool isPriority) -> void; + auto RevokeGrant(const std::string &permission, bool isPriority) -> void; + auto RevokeDenial(const std::string &permission, bool isPriority) -> void; + auto AddPermissionRule(const std::string &permission, bool isGrant, bool isPriority) -> void; + auto RemovePermissionRule(const std::string &permission, bool isGrant, bool isPriority) -> void; + + friend class caosdb::connection::Connection; + +private: + std::unique_ptr<RoleImpl> wrapped; +}; + +} // namespace caosdb::acm +#endif +#endif diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index d0414f2e84f81fe9e86a50f2e6411788cf0befeb..1f47cc045baf3f0217c315e5d9e9b4e826007d54 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -28,6 +28,7 @@ * @brief Configuration and setup of the connection. */ #ifdef BUILD_ACM +#include "caosdb/acm/role.h" // for Role #include "caosdb/acm/user.h" // for User #include "caosdb/acm/v1alpha1/main.grpc.pb.h" // for AccessControlMan... #endif @@ -43,9 +44,11 @@ #include <map> // for map #include <memory> // for shared_ptr, unique_ptr #include <string> // for string, basic_string +#include <vector> // for vector namespace caosdb::connection { #ifdef BUILD_ACM +using caosdb::acm::Role; using caosdb::acm::User; using caosdb::acm::v1alpha1::AccessControlManagementService; #endif @@ -103,6 +106,17 @@ public: [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>; #ifdef BUILD_ACM + /** + * List all known roles. + */ + [[nodiscard]] auto ListRoles() const -> std::vector<Role>; + + [[nodiscard]] auto RetrieveSingleRole(const std::string &name) const -> Role; + + auto CreateSingleRole(const Role &role) const -> void; + + auto DeleteSingleRole(const std::string &name) const -> void; + /** * Retrieve a single user. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f64bc7671cbe142390ca5439916dfbddd3660294..f2917635ea619755e50bd2291b436d66820e226f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,9 @@ set(libcaosdb_SRC IF(BUILD_ACM) list(APPEND libcaosdb_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/permission_rule.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role_impl.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user_impl.h ) diff --git a/src/caosdb/acm/permission_rule.cpp b/src/caosdb/acm/permission_rule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bc4cb2f09478051165002d26abb60c23683dc1b3 --- /dev/null +++ b/src/caosdb/acm/permission_rule.cpp @@ -0,0 +1,91 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/acm/permission_rule.h" +#include "caosdb/acm/permission_rule_impl.h" // for PermissionRuleImpl +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListPermissionRulesRequest +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <type_traits> // for remove_reference<>::type +#include <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoPermissionRule = caosdb::acm::v1alpha1::PermissionRule; + +PermissionRuleImpl::PermissionRuleImpl() = default; + +PermissionRuleImpl::PermissionRuleImpl(ProtoPermissionRule *rule) + : ScalarProtoMessageWrapper<ProtoPermissionRule>(rule) {} + +PermissionRuleImpl::PermissionRuleImpl(std::string permission, bool isGrant, bool isPriority) { + this->wrapped->set_permission(std::move(permission)); + this->wrapped->set_grant(isGrant); + this->wrapped->set_priority(isPriority); +} + +PermissionRule::PermissionRule(std::string permission, bool isGrant, bool isPriority) + : impl(new PermissionRuleImpl(std::move(permission), isGrant, isPriority)) {} + +PermissionRule::PermissionRule(const PermissionRule &rule) : impl(new PermissionRuleImpl()) { + this->impl->wrapped->CopyFrom(*(rule.impl->wrapped)); +} + +auto PermissionRule::operator=(const PermissionRule &rule) -> PermissionRule & { + if (this == &rule) { + return *this; + } + this->impl->wrapped->CopyFrom(*(rule.impl->wrapped)); + + return *this; +} + +PermissionRule::~PermissionRule() { delete this->impl; } + +auto PermissionRule::ToString() const -> std::string { return this->impl->ToString(); } + +[[nodiscard]] auto PermissionRule::IsGrant() const -> bool { return this->impl->wrapped->grant(); } + +auto PermissionRule::SetGrant(bool isGrant) -> void { this->impl->wrapped->set_grant(isGrant); } + +[[nodiscard]] auto PermissionRule::IsPriority() const -> bool { + return this->impl->wrapped->priority(); +} + +auto PermissionRule::SetPriority(bool isPriority) -> void { + this->impl->wrapped->set_priority(isPriority); +} + +auto PermissionRule::operator==(const PermissionRule &other) const -> bool { + if (this == &other) { + return true; + } + return this->GetPermission() == other.GetPermission() && + this->IsPriority() == other.IsPriority() && this->IsGrant() == other.IsGrant(); +} + +auto HashPermissionRule::operator()(const PermissionRule &rule) const -> size_t { + return std::hash<std::string>()(rule.GetPermission()); +} + +auto PermissionRule::GetPermission() const -> const std::string & { + return this->impl->wrapped->permission(); +} + +} // namespace caosdb::acm diff --git a/src/caosdb/acm/permission_rule_impl.h b/src/caosdb/acm/permission_rule_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..b8e99f5efdce5c68081ec196ab2afbe67ad970a4 --- /dev/null +++ b/src/caosdb/acm/permission_rule_impl.h @@ -0,0 +1,51 @@ + +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#ifndef CAOSDB_ACM_PERMISSION_RULE_IMPL_H +#define CAOSDB_ACM_PERMISSION_RULE_IMPL_H + +#include "caosdb/acm/role.h" +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListRolesRequest +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoPermissionRule = caosdb::acm::v1alpha1::PermissionRule; + +/** + * PermissionRuleImpl class is designed to hide the implementation which makes direct use + * of the protobuf objects underneath from the clients of the caosdb library. + */ +class PermissionRuleImpl : public ScalarProtoMessageWrapper<ProtoPermissionRule> { +public: + /** + * Constructor. Instanciate a role from the server's responces. + */ + PermissionRuleImpl(ProtoPermissionRule *rule); + PermissionRuleImpl(); + PermissionRuleImpl(std::string permission, bool isGrant, bool isPriority); + + friend class PermissionRule; +}; + +} // namespace caosdb::acm +#endif diff --git a/src/caosdb/acm/role.cpp b/src/caosdb/acm/role.cpp new file mode 100644 index 0000000000000000000000000000000000000000..23b919f6249feaa5066f28f85a52d0e937b69d90 --- /dev/null +++ b/src/caosdb/acm/role.cpp @@ -0,0 +1,156 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/acm/role.h" +#include "caosdb/acm/permission_rule.h" // for PermissionRule +#include "caosdb/acm/role_impl.h" // for RoleImpl +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListRolesRequest +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <unordered_set> // for _Node_iterator, operator!= +#include <utility> // for move +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoRole = caosdb::acm::v1alpha1::Role; +using ProtoRolePermissions = caosdb::acm::v1alpha1::RolePermissions; +using ProtoRoleCapabilities = caosdb::acm::v1alpha1::RoleCapabilities; +using ProtoListRoleItem = caosdb::acm::v1alpha1::ListRoleItem; + +RoleImpl::RoleImpl(const std::string &name, const std::string &description) { + if (!name.empty()) { + this->wrapped->set_name(name); + } + if (!description.empty()) { + this->wrapped->set_description(description); + } +} + +RoleImpl::RoleImpl(ProtoRole *role) : ScalarProtoMessageWrapper<ProtoRole>(role) {} + +RoleImpl::RoleImpl(ProtoListRoleItem &role_item) + : ScalarProtoMessageWrapper<ProtoRole>(role_item.release_role()) {} + +RoleImpl::RoleImpl(const std::string &name) : RoleImpl(name, "") {} + +[[nodiscard]] auto RoleImpl::GeneratePermissionRulesSet() -> std::unique_ptr<PermissionRules> { + auto result = std::make_unique<PermissionRules>(); + for (auto const &rule : this->wrapped->permission_rules()) { + result->emplace(rule.permission(), rule.grant(), rule.priority()); + } + return result; +} + +Role::Role(std::unique_ptr<RoleImpl> wrapped) : wrapped(std::move(wrapped)) {} + +Role::Role(const std::string &name, const std::string &description) + : wrapped(std::make_unique<RoleImpl>(name, description)) {} + +Role::Role(const std::string &name) : Role(name, {""}) {} + +Role::Role(const Role &role) : Role(role.GetName(), role.GetDescription()) {} + +auto Role::operator=(const Role &role) -> Role & { + if (this == &role) { + return *this; + } + this->wrapped->wrapped->CopyFrom(*(role.wrapped->wrapped)); + + return *this; +} + +Role::~Role() = default; + +auto Role::GetName() const -> const std::string & { return this->wrapped->wrapped->name(); } + +auto Role::SetName(const std::string &name) -> void { + if (!name.empty()) { + this->wrapped->wrapped->set_name(name); + } else { + this->wrapped->wrapped->clear_name(); + } +} + +auto Role::GetDescription() const -> const std::string & { + return this->wrapped->wrapped->description(); +} + +auto Role::SetDescription(const std::string &description) -> void { + if (!description.empty()) { + this->wrapped->wrapped->set_description(description); + } else { + this->wrapped->wrapped->clear_description(); + } +} + +auto Role::ToString() const -> std::string { return this->wrapped->ToString(); } + +[[nodiscard]] auto Role::GetPermissionRules() const -> const PermissionRules & { + if (this->wrapped->rules == nullptr) { + this->wrapped->rules = this->wrapped->GeneratePermissionRulesSet(); + } + return *(this->wrapped->rules); +} + +auto Role::Grant(const std::string &permission, bool isPriority) -> void { + this->AddPermissionRule(permission, true, isPriority); +} + +auto Role::RevokeGrant(const std::string &permission, bool isPriority) -> void { + this->RemovePermissionRule(permission, true, isPriority); +} + +auto Role::Deny(const std::string &permission, bool isPriority) -> void { + this->AddPermissionRule(permission, false, isPriority); +} + +auto Role::RevokeDenial(const std::string &permission, bool isPriority) -> void { + this->RemovePermissionRule(permission, false, isPriority); +} + +auto Role::AddPermissionRule(const std::string &permission, bool isGrant, bool isPriority) -> void { + this->RemovePermissionRule(permission, isGrant, isPriority); + auto *new_rule = this->wrapped->wrapped->add_permission_rules(); + new_rule->set_permission(permission); + new_rule->set_priority(isPriority); + new_rule->set_grant(isGrant); +} + +auto Role::RemovePermissionRule(const std::string &permission, bool isGrant, bool isPriority) + -> void { + this->wrapped->rules.reset(); + auto rule = this->wrapped->wrapped->mutable_permission_rules()->begin(); + while (rule != this->wrapped->wrapped->mutable_permission_rules()->end()) { + if (rule->grant() == isGrant && rule->priority() == isPriority && + rule->permission() == permission) { + this->wrapped->wrapped->mutable_permission_rules()->erase(rule); + rule = this->wrapped->wrapped->mutable_permission_rules()->begin(); + } else { + rule++; + } + } +} + +auto Role::ClearPermissionRules() -> void { + this->wrapped->rules.reset(); + this->wrapped->wrapped->clear_permission_rules(); +} + +} // namespace caosdb::acm diff --git a/src/caosdb/acm/role_impl.h b/src/caosdb/acm/role_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..090de2255599c1cba2463e860a94e75cb54e5875 --- /dev/null +++ b/src/caosdb/acm/role_impl.h @@ -0,0 +1,66 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/acm/role.h" +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListRolesRequest +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <memory> // for unique_ptr +#include <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoRole = caosdb::acm::v1alpha1::Role; +using ProtoRolePermissions = caosdb::acm::v1alpha1::RolePermissions; +using ProtoRoleCapabilities = caosdb::acm::v1alpha1::RoleCapabilities; +using ProtoListRoleItem = caosdb::acm::v1alpha1::ListRoleItem; + +/** + * RoleImpl class is designed to hide the implementation which makes direct use + * of the protobuf objects underneath from the clients of the caosdb library. + */ +class RoleImpl : public ScalarProtoMessageWrapper<ProtoRole> { +public: + /** + * Constructor. Instanciate a role with the given name. + */ + explicit RoleImpl(const std::string &name); + /** + * Constructor. Instanciate a role with the given name and description. + */ + RoleImpl(const std::string &name, const std::string &description); + /** + * Constructor. Instanciate a role from the server's responces. + */ + RoleImpl(ProtoRole *role); + + /** + * Constructor. Instanciate a role from a listRoles RPC. + */ + RoleImpl(ProtoListRoleItem &role); + + friend class Role; + friend class caosdb::connection::Connection; + +private: + std::unique_ptr<PermissionRules> rules; + [[nodiscard]] auto GeneratePermissionRulesSet() -> std::unique_ptr<PermissionRules>; +}; + +} // namespace caosdb::acm diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index abc87dacb054a570540eff7e896eb1058b7b267b..b81eff3017ab53c1c13c3245f54a64a14ef6d843 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -21,9 +21,10 @@ */ #include "caosdb/connection.h" #ifdef BUILD_ACM +#include "caosdb/acm/role_impl.h" // for RoleImpl #include "caosdb/acm/user_impl.h" // for UserImpl #include "caosdb/acm/v1alpha1/main.grpc.pb.h" // for AccessControlMan... -#include "caosdb/acm/v1alpha1/main.pb.h" // for CreateSingleUser... +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListRolesRequest #endif #include "caosdb/configuration.h" // for ConnectionConfigur... #include "caosdb/exceptions.h" // for ConfigurationError @@ -41,12 +42,21 @@ namespace caosdb::connection { #ifdef BUILD_ACM +using caosdb::acm::RoleImpl; using caosdb::acm::UserImpl; using caosdb::acm::v1alpha1::AccessControlManagementService; +using caosdb::acm::v1alpha1::CreateSingleRoleRequest; +using caosdb::acm::v1alpha1::CreateSingleRoleResponse; using caosdb::acm::v1alpha1::CreateSingleUserRequest; using caosdb::acm::v1alpha1::CreateSingleUserResponse; +using caosdb::acm::v1alpha1::DeleteSingleRoleRequest; +using caosdb::acm::v1alpha1::DeleteSingleRoleResponse; using caosdb::acm::v1alpha1::DeleteSingleUserRequest; using caosdb::acm::v1alpha1::DeleteSingleUserResponse; +using caosdb::acm::v1alpha1::ListRolesRequest; +using caosdb::acm::v1alpha1::ListRolesResponse; +using caosdb::acm::v1alpha1::RetrieveSingleRoleRequest; +using caosdb::acm::v1alpha1::RetrieveSingleRoleResponse; using caosdb::acm::v1alpha1::RetrieveSingleUserRequest; using caosdb::acm::v1alpha1::RetrieveSingleUserResponse; #endif @@ -118,6 +128,87 @@ auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { } #ifdef BUILD_ACM +[[nodiscard]] auto Connection::RetrieveSingleRole(const std::string &name) const -> Role { + RetrieveSingleRoleRequest request; + request.set_name(name); + RetrieveSingleRoleResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->RetrieveSingleRole(&context, request, &response); + + auto status = TransactionStatus::SUCCESS(); + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { + case grpc::StatusCode::UNAUTHENTICATED: + status = TransactionStatus::AUTHENTICATION_ERROR(grpc_status.error_message()); + break; + case grpc::StatusCode::UNAVAILABLE: + status = TransactionStatus::CONNECTION_ERROR(); + break; + default: + auto error_message = grpc_status.error_message(); + status = TransactionStatus::RPC_ERROR(std::to_string(grpc_status.error_code()) + " - " + + error_message); + } + } + status.ThrowExceptionIfError(); + auto *role = response.release_role(); + return Role(std::make_unique<RoleImpl>(role)); +} + +auto Connection::DeleteSingleRole(const std::string &name) const -> void { + DeleteSingleRoleRequest request; + request.set_name(name); + DeleteSingleRoleResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->DeleteSingleRole(&context, request, &response); + + auto status = TransactionStatus::SUCCESS(); + std::vector<Role> result; + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { + case grpc::StatusCode::UNAUTHENTICATED: + status = TransactionStatus::AUTHENTICATION_ERROR(grpc_status.error_message()); + break; + case grpc::StatusCode::UNAVAILABLE: + status = TransactionStatus::CONNECTION_ERROR(); + break; + default: + auto error_message = grpc_status.error_message(); + status = TransactionStatus::RPC_ERROR(std::to_string(grpc_status.error_code()) + " - " + + error_message); + } + } + status.ThrowExceptionIfError(); +} + +auto Connection::CreateSingleRole(const Role &role) const -> void { + CreateSingleRoleRequest request; + request.set_allocated_role(role.wrapped->wrapped); + CreateSingleRoleResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->CreateSingleRole(&context, request, &response); + + auto status = TransactionStatus::SUCCESS(); + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { + case grpc::StatusCode::UNAUTHENTICATED: + status = TransactionStatus::AUTHENTICATION_ERROR(grpc_status.error_message()); + break; + case grpc::StatusCode::UNAVAILABLE: + status = TransactionStatus::CONNECTION_ERROR(); + break; + default: + auto error_message = grpc_status.error_message(); + status = TransactionStatus::RPC_ERROR(std::to_string(grpc_status.error_code()) + " - " + + error_message); + } + } + status.ThrowExceptionIfError(); +} + // TODO(tf) find a way to deal with this: // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) [[nodiscard]] auto Connection::RetrieveSingleUser(const std::string &realm, @@ -189,6 +280,33 @@ auto Connection::CreateSingleUser(const User &user) const -> void { this->access_controll_management_service->CreateSingleUser(&context, request, &response); auto status = TransactionStatus::SUCCESS(); + std::vector<Role> result; + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { + case grpc::StatusCode::UNAUTHENTICATED: + status = TransactionStatus::AUTHENTICATION_ERROR(grpc_status.error_message()); + break; + case grpc::StatusCode::UNAVAILABLE: + status = TransactionStatus::CONNECTION_ERROR(); + break; + default: + auto error_message = grpc_status.error_message(); + status = TransactionStatus::RPC_ERROR(std::to_string(grpc_status.error_code()) + " - " + + error_message); + } + } + status.ThrowExceptionIfError(); +} + +[[nodiscard]] auto Connection::ListRoles() const -> std::vector<Role> { + const ListRolesRequest request; + ListRolesResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->ListRoles(&context, request, &response); + + auto status = TransactionStatus::SUCCESS(); + std::vector<Role> result; if (!grpc_status.ok()) { switch (grpc_status.error_code()) { case grpc::StatusCode::UNAUTHENTICATED: @@ -204,7 +322,14 @@ auto Connection::CreateSingleUser(const User &user) const -> void { } } status.ThrowExceptionIfError(); + + auto *roles = response.mutable_roles(); + for (auto &role_item : *roles) { + result.emplace_back(std::make_unique<RoleImpl>(role_item)); + } + return result; } + #endif auto ConnectionManager::mHasConnection(const std::string &name) const -> bool { diff --git a/src/cxxcaosdbcli.cpp b/src/cxxcaosdbcli.cpp index e6e5cee66e6c90ab7719434d7667cd6ad20ccbb8..8047050bc2480344db39cf5961228a221562542a 100644 --- a/src/cxxcaosdbcli.cpp +++ b/src/cxxcaosdbcli.cpp @@ -21,6 +21,9 @@ */ // A simple caosdb client +#ifdef BUILD_ACM +#include "caosdb/acm/role.h" // for Role +#endif #include "caosdb/connection.h" // for Connection, ConnectionManager #include "caosdb/constants.h" // for LIBCAOSDB_VERSION_MINOR, LIBCAOSDB_V... #include "caosdb/entity.h" // for Entity @@ -29,59 +32,153 @@ #include "caosdb/logging.h" // for CAOSDB_LOG_TRACE #include "caosdb/transaction.h" // for Transaction, ResultSet #include "caosdb/transaction_status.h" // for TransactionSt... -#include <boost/log/core/record.hpp> // for record -#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... -#include <boost/log/sources/record_ostream.hpp> // for operator<< -#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... -#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... -#include <exception> // for exception -#include <iostream> // for operator<<, basic_ostream, basic_ost... -#include <memory> // for unique_ptr, allocator, __shared_ptr_... -#include <string> // for operator<<, char_traits - +#include <boost/program_options.hpp> // for options_description +#include <exception> // for exception +#include <iostream> // for operator<<, basic_ostream, basic_ost... +#include <memory> // for unique_ptr, allocator, __shared_ptr_... +#include <string> // for operator<<, char_traits +#include <vector> // for vector const auto logger_name = "libcaosdb"; -auto main() -> int { +using boost::program_options::bool_switch; +using boost::program_options::command_line_parser; +using boost::program_options::notify; +using boost::program_options::options_description; +using boost::program_options::store; +using boost::program_options::value; +using boost::program_options::variables_map; +#ifdef BUILD_ACM +using caosdb::acm::Role; +#endif - std::cout << "CaosDB C++ client (libcaosdb " << caosdb::LIBCAOSDB_VERSION_MINOR << "." - << caosdb::LIBCAOSDB_VERSION_MINOR << "." << caosdb::LIBCAOSDB_VERSION_PATCH << ")\n" - << "We don't miss the H of caos.\n" +auto print_version(bool print) -> void { + if (print) { + std::cout << "CaosDB C++ client (libcaosdb " << caosdb::LIBCAOSDB_VERSION_MAJOR << "." + << caosdb::LIBCAOSDB_VERSION_MINOR << "." << caosdb::LIBCAOSDB_VERSION_PATCH << ")\n" + << "We don't miss the H of caos.\n" + << std::endl; + } +} + +auto test_connection() -> void { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + + connection->RetrieveVersionInfoNoExceptions(); + // get version info of the server + const auto &v_info = connection->GetVersionInfo(); + std::cout << "Server Version: " << v_info->GetMajor() << "." << v_info->GetMinor() << "." + << v_info->GetPatch() << "-" << v_info->GetPreRelease() << "-" << v_info->GetBuild() << std::endl; +} + +auto retrieve_entity_by_id(const std::string &id) -> void { + std::cout << "Retrieve entity " << id << std::endl; + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + auto transaction(connection->CreateTransaction()); + transaction->RetrieveById(id); + transaction->ExecuteAsynchronously(); + auto t_stat = transaction->WaitForIt(); + CAOSDB_LOG_INFO(logger_name) << "status: " << t_stat.GetCode() << " // " + << t_stat.GetDescription(); + const auto &result_set = transaction->GetResultSet(); + for (const auto &entity : result_set) { + std::cout << entity.ToString() << std::endl; + } +} + +auto execute_query(const std::string &query) -> void { + std::cout << "Execute query:\n" << query << std::endl; + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + auto q_transaction(connection->CreateTransaction()); + q_transaction->Query(query); + q_transaction->ExecuteAsynchronously(); + auto t_stat = q_transaction->WaitForIt(); + CAOSDB_LOG_INFO(logger_name) << "status: " << t_stat.GetCode() << " // " + << t_stat.GetDescription(); + const auto &result_set_2 = q_transaction->GetResultSet(); + for (const auto &entity : result_set_2) { + std::cout << entity.ToString() << std::endl; + } +} +#ifdef BUILD_ACM +auto list_roles() -> void { + std::cout << "Known roles:" << std::endl; + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + auto roles = connection->ListRoles(); + for (const auto &role : roles) { + std::cout << role.ToString() << std::endl; + } +} + +auto retrieve_role(const std::string &name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + auto role = connection->RetrieveSingleRole(name); + std::cout << role.ToString() << std::endl; +} + +auto create_role(const std::string &name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + Role role(name); + connection->CreateSingleRole(role); + std::cout << "OK" << std::endl; +} + +auto delete_role(const std::string &name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + connection->DeleteSingleRole(name); + std::cout << "OK" << std::endl; +} +#endif + +auto main(int argc, const char *argv[]) -> int { try { - const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + command_line_parser parser(argc, argv); - connection->RetrieveVersionInfoNoExceptions(); - // get version info of the server - const auto &v_info = connection->GetVersionInfo(); - std::cout << "Server Version: " << v_info->GetMajor() << "." << v_info->GetMinor() << "." - << v_info->GetPatch() << "-" << v_info->GetPreRelease() << "-" << v_info->GetBuild() - << std::endl; + options_description desc{"Options"}; + desc.add_options()("help,h", "Show help.")("version,V", bool_switch()->notifier(print_version), + "Print version of libcaosdb.")( + "test-connection,t", "Test the connection to the caosdb server.")( + "retrieve", value<std::string>()->notifier(retrieve_entity_by_id), + "Retrieve an entity by id and print its JSON representation.")( + "execute-query", value<std::string>()->notifier(execute_query), + "Execute a query and print the results") +#ifdef BUILD_ACM + ("list-roles", "List all known roles")( + "retrieve-role", value<std::string>()->notifier(retrieve_role), "Retrieve a role by name")( + "create-role", value<std::string>()->notifier(create_role), "Create a new role")( + "delete-role", value<std::string>()->notifier(delete_role), "Create a new role") +#endif + ; - // retrieve an entity - auto transaction(connection->CreateTransaction()); - transaction->RetrieveById("21"); - transaction->ExecuteAsynchronously(); - auto t_stat = transaction->WaitForIt(); - CAOSDB_LOG_INFO(logger_name) << "status: " << t_stat.GetCode() << " // " - << t_stat.GetDescription(); - const auto &result_set = transaction->GetResultSet(); - for (const auto &entity : result_set) { - std::cout << entity.ToString() << std::endl; - } + parser.options(desc); + + // TODO(tf) add positional parameters + // positional_options_description pos_desc; + // pos_desc.add("phone", 1); + // parser.positional(pos_desc); + + variables_map vm; + store(parser.run(), vm); + notify(vm); - // execute a query - std::string query("FIND Property \"Prop *\""); - std::cout << "Trying to execute a query:\n" << query << std::endl; - auto q_transaction(connection->CreateTransaction()); - q_transaction->Query(query); - q_transaction->ExecuteAsynchronously(); - t_stat = q_transaction->WaitForIt(); - CAOSDB_LOG_INFO(logger_name) << "status: " << t_stat.GetCode() << " // " - << t_stat.GetDescription(); - const auto &result_set_2 = q_transaction->GetResultSet(); - for (const auto &entity : result_set_2) { - std::cout << entity.ToString() << std::endl; + if (vm.count("help") != 0U) { + std::cout << desc << std::endl; + } else if (vm["version"].as<bool>()) { + } else if (vm.count("test-connection") != 0U) { + test_connection(); +#ifdef BUILD_ACM + } else if (vm.count("list-roles") != 0U) { + list_roles(); + } else if (vm.count("retrieve-role") != 0U) { + } else if (vm.count("retrieve") != 0U) { + } else if (vm.count("create-role") != 0U) { + } else if (vm.count("delete-role") != 0U) { + } else if (vm.count("execute-query") != 0U) { +#endif + } else { + print_version(true); + test_connection(); } return 0; @@ -92,7 +189,7 @@ auto main() -> int { std::cout << "Exception: " << exc.what() << std::endl; return 1; } catch (...) { - std::cout << "Some other exception." << std::endl; + std::cout << "Unknown error." << std::endl; return 2; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 659254bf1e215c6c203eb82c0d2bca030a12a0b9..ab517063ccc31828d251e93c55ceafa0a2ed5e78 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ set(test_cases IF(BUILD_ACM) list(APPEND test_cases + test_role test_user ) ENDIF() diff --git a/test/test_connection.cpp b/test/test_connection.cpp index 9b8783a0ae88f6669de4e5ec4e5a43dea26447c5..0426c35d675bc7ddfe20e7d0edeb069e6277f40e 100644 --- a/test/test_connection.cpp +++ b/test/test_connection.cpp @@ -117,6 +117,13 @@ TEST_F(test_connection, test_retrieve_single_user) { "The attempt to execute this transaction was not successful because the " "connection to the server could not be established."); } + +TEST_F(test_connection, test_list_roles) { + auto connection = ConnectionManager::GetDefaultConnection(); + EXPECT_THROW_MESSAGE(auto results = connection->ListRoles(), caosdb::exceptions::ConnectionError, + "The attempt to execute this transaction was not successful because the " + "connection to the server could not be established."); +} #endif } // namespace caosdb::connection diff --git a/test/test_role.cpp b/test/test_role.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dc9a8b825e08a991eb220dd07b589a454cfddb03 --- /dev/null +++ b/test/test_role.cpp @@ -0,0 +1,131 @@ +/* + * + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#include "caosdb/acm/role.h" // for Role +#include "caosdb/acm/permission_rule.h" // for PermissionRules, PermissionRule +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiRe... +#include <gtest/gtest_pred_impl.h> // for Test, EXPECT_EQ, TEST +#include <string> // for allocator, string +#include <unordered_set> // for _Node_const_iterator, operat... + +namespace caosdb::acm { + +TEST(test_role, role_name) { + auto role = Role("role1"); + EXPECT_EQ(role.GetName(), "role1"); + role.SetName("role2"); + EXPECT_EQ(role.GetName(), "role2"); +} + +TEST(test_role, role_description) { + auto role1 = Role("role1", "description1"); + EXPECT_EQ(role1.GetName(), "role1"); + EXPECT_EQ(role1.GetDescription(), "description1"); + role1.SetName("role3"); + EXPECT_EQ(role1.GetDescription(), "description1"); + + auto role2 = Role("role2", "description1"); + EXPECT_EQ(role2.GetDescription(), "description1"); + EXPECT_EQ(role2.GetName(), "role2"); + role2.SetDescription("description2"); + EXPECT_EQ(role2.GetDescription(), "description2"); + EXPECT_EQ(role2.GetName(), "role2"); +} + +TEST(test_role, grant_revoke) { + auto role1 = Role("role1", "description1"); + EXPECT_TRUE(role1.GetPermissionRules().empty()); + EXPECT_EQ(role1.ToString(), "{\n \"name\": \"role1\",\n \"description\": \"description1\"\n}\n"); + + role1.Grant("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + + EXPECT_EQ(role1.ToString(), + "{\n \"name\": \"role1\",\n \"description\": \"description1\",\n \"permissionRules\": " + "[\n {\n \"permission\": \"SOME_PERMISSION\",\n \"grant\": true\n }\n ]\n}\n"); + + role1.Grant("SOME_OTHER_PERMISSION", true); + EXPECT_EQ(role1.GetPermissionRules().size(), 2); + + role1.Grant("SOME_OTHER_PERMISSION", true); + // is a set + EXPECT_EQ(role1.GetPermissionRules().size(), 2); + + role1.RevokeGrant("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + role1.RevokeGrant("NOT_EXISTING_PERMISSION", true); + + // is immutable + for (const auto &rule : role1.GetPermissionRules()) { + EXPECT_EQ(rule.GetPermission(), "SOME_OTHER_PERMISSION"); + } + + role1.RevokeGrant("SOME_OTHER_PERMISSION", true); + EXPECT_TRUE(role1.GetPermissionRules().empty()); + + EXPECT_EQ(role1.ToString(), "{\n \"name\": \"role1\",\n \"description\": \"description1\"\n}\n"); +} + +TEST(test_role, deny_revoke) { + auto role1 = Role("role1", "description1"); + EXPECT_TRUE(role1.GetPermissionRules().empty()); + EXPECT_EQ(role1.ToString(), "{\n \"name\": \"role1\",\n \"description\": \"description1\"\n}\n"); + + role1.Deny("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + + EXPECT_EQ(role1.ToString(), + "{\n \"name\": \"role1\",\n \"description\": \"description1\",\n \"permissionRules\": " + "[\n {\n \"permission\": \"SOME_PERMISSION\"\n }\n ]\n}\n"); + + role1.Deny("SOME_OTHER_PERMISSION", true); + EXPECT_EQ(role1.GetPermissionRules().size(), 2); + + role1.Deny("SOME_OTHER_PERMISSION", true); + // is a set + EXPECT_EQ(role1.GetPermissionRules().size(), 2); + + role1.RevokeDenial("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + role1.RevokeDenial("NOT_EXISTING_PERMISSION", true); + + // is immutable + for (const auto &rule : role1.GetPermissionRules()) { + EXPECT_EQ(rule.GetPermission(), "SOME_OTHER_PERMISSION"); + } + + role1.ClearPermissionRules(); + EXPECT_TRUE(role1.GetPermissionRules().empty()); + + EXPECT_EQ(role1.ToString(), "{\n \"name\": \"role1\",\n \"description\": \"description1\"\n}\n"); +} + +TEST(test_role, permission_rule) { + auto rule = PermissionRule("SOME_PERMISSION", true, false); + EXPECT_EQ(rule.GetPermission(), "SOME_PERMISSION"); + EXPECT_TRUE(rule.IsGrant()); + EXPECT_FALSE(rule.IsPriority()); + EXPECT_EQ(rule.ToString(), "{\n \"permission\": \"SOME_PERMISSION\",\n \"grant\": true\n}\n"); +} + +} // namespace caosdb::acm