diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ff3f9ab6a3048b9a51ee57cf43ce30134c92d42..8d762b328ef148b898a47a4a975cb83fb9e3b73d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ add_subdirectory(doc) set(PROTO_FILES ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/info/v1/main.proto ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/entity/v1/main.proto + ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/acm/v1alpha1/main.proto ) set(PROTO_PATH ${PROJECT_SOURCE_DIR}/proto/proto) @@ -173,6 +174,7 @@ target_link_libraries(caosdb target_include_directories(caosdb PUBLIC $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/include> $<BUILD_INTERFACE:${libcaosdb_BINARY_DIR}/include> + $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/src> $<INSTALL_INTERFACE:include> ) target_include_directories(caosdb SYSTEM PUBLIC diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 784e2e04ae3baa8aacffbacbba2ecc55c1c0af6a..094b02ad8329fd0de847d015fd22e482ab8adbd5 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -20,6 +20,7 @@ # add all header files to this list set(libcaosdb_INCL + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/authentication.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/certificate_provider.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/configuration.h diff --git a/include/caosdb/acm/permission_rule.h b/include/caosdb/acm/permission_rule.h new file mode 100644 index 0000000000000000000000000000000000000000..0ea11218f40a0ae4e7fa167d866b0e4fbebd1f2f --- /dev/null +++ b/include/caosdb/acm/permission_rule.h @@ -0,0 +1,67 @@ +/* + * 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 + */ +#ifndef CAOSDB_ACM_PERMISSION_RULE_H +#define CAOSDB_ACM_PERMISSION_RULE_H + +#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 -> const bool; + auto SetGrant(bool isGrant) -> void; + [[nodiscard]] auto IsPriority() const -> 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 { + size_t operator()(const PermissionRule& rule) const { + return std::hash<std::string>()(rule.GetPermission()); + } +}; + +using PermissionRules = std::unordered_set<PermissionRule, HashPermissionRule>; + +} // namespace caosdb::acm +#endif diff --git a/include/caosdb/acm/role.h b/include/caosdb/acm/role.h new file mode 100644 index 0000000000000000000000000000000000000000..feb2138d0a1d671f9026d4e3df82d0807902884b --- /dev/null +++ b/include/caosdb/acm/role.h @@ -0,0 +1,74 @@ +/* + * 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 + */ +#ifndef CAOSDB_ACM_ROLES_H +#define CAOSDB_ACM_ROLES_H + +#include "caosdb/acm/permission_rule.h" // for PermissionRule +#include <string> // for string +#include <vector> // for vector + +namespace caosdb::connection { +class Connection; +} + +namespace caosdb::acm { + +class RoleImpl; + +class Role { +public: + explicit Role(std::string name); + explicit Role(RoleImpl *wrapped); + Role(std::string name, 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(std::string name) -> void; + [[nodiscard]] auto GetDescription() const -> const std::string &; + auto SetDescription(std::string description) -> void; + // TODO(tf) declare and implement: + [[nodiscard]] auto GetPermissionRules() const -> const PermissionRules &; + // auto SetPermissionRules(PermissionRules rules) -> void; + // auto ClearPermissionRules() -> void; + auto Grant(std::string permission, bool priority) -> void; + // auto Deny(std::string permission, bool priority) -> void; + auto RevokeGrant(std::string permission, bool priority) -> void; + // auto RevokeDenial(std::string permission, bool priority) -> void; + + friend class caosdb::connection::Connection; + +private: + RoleImpl *wrapped; +}; + +} // namespace caosdb::acm +#endif diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index ac1d05a419816ac21bd0a70ceafaf97428bd08da..1a833a1896ed79a3ba5570482c60f2bcf8595582 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -27,20 +27,25 @@ * @date 2021-05-18 * @brief Configuration and setup of the connection. */ -#include "caosdb/authentication.h" // for Authenticator -#include "caosdb/configuration.h" // for ConnectionConfigura... -#include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransactionSe... -#include "caosdb/info.h" // for VersionInfo -#include "caosdb/info/v1/main.grpc.pb.h" // for GeneralInfoService:... -#include "caosdb/transaction.h" // for Transaction -#include "caosdb/transaction_status.h" // for TransactionStatus -#include <filesystem> // for path -#include <grpcpp/channel.h> // for Channel -#include <map> // for map -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string, basic_string +#include "caosdb/acm/role.h" // for Role +#include "caosdb/acm/v1alpha1/main.grpc.pb.h" // for AccessControlMan... +#include "caosdb/authentication.h" // for Authenticator +#include "caosdb/configuration.h" // for ConnectionConfigura... +#include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransactionSe... +#include "caosdb/info.h" // for VersionInfo +#include "caosdb/info/v1/main.grpc.pb.h" // for GeneralInfoService:... +#include "caosdb/transaction.h" // for Transaction +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <filesystem> // for path +#include <grpcpp/channel.h> // for Channel +#include <map> // for map +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string, basic_string +#include <vector> // for vector namespace caosdb::connection { +using caosdb::acm::Role; +using caosdb::acm::v1alpha1::AccessControlManagementService; using caosdb::authentication::Authenticator; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1::EntityTransactionService; @@ -89,8 +94,22 @@ public: return this->version_info.get(); }; + /** + * Create a new transaction object which uses this connection and return it. + */ [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>; + /** + * List all known roles. + */ + [[nodiscard]] auto ListRoles() const -> std::vector<Role>; + + [[nodiscard]] auto RetrieveSingleRole(std::string name) const -> Role; + + auto CreateSingleRole(const Role &role) const -> void; + + auto DeleteSingleRole(std::string name) const -> void; + private: /// GRPC-Channel (HTTP/2 Connection plus Authentication). We use a shared /// pointer because Transaction instances also own the channel. @@ -107,6 +126,10 @@ private: /// Service for file transmission (download and upload). We use a shared /// pointer because Transaction instances also own this service stub. std::shared_ptr<FileTransmissionService::Stub> file_transmission_service; + /// Service for Access Controll Management (Role, Useraccounts, Permissions). + /// We use a unique pointer because only this connection owns and uses this + /// service. + std::unique_ptr<AccessControlManagementService::Stub> access_controll_management_service; }; /** diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index 32722a843e0724e768d4e05d531206052e7a12de..52a69e05083670a302a71b883993ae28122e7655 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -707,7 +707,7 @@ public: : static_cast<ProtoDataType *>(nullptr)) { properties.wrapped = this->wrapped->mutable_properties(); parents.wrapped = this->wrapped->mutable_parents(); - if(this->wrapped->has_file_descriptor()) { + if (this->wrapped->has_file_descriptor()) { file_descriptor.wrapped = this->wrapped->mutable_file_descriptor(); } }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3654e01f5aa6f184338ff74070755df87f9147c..56269b910838ef3ac008ce837c8d0137bd60bad1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,9 @@ # add all source files to this list set(libcaosdb_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/role_impl.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/permission_rule.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/authentication.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.cpp diff --git a/src/caosdb/acm/permission_rule.cpp b/src/caosdb/acm/permission_rule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ce21977d45b7e0ce59c7261787d87eeb44de3c4 --- /dev/null +++ b/src/caosdb/acm/permission_rule.cpp @@ -0,0 +1,82 @@ +/* + * 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 <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoPermissionRule = caosdb::acm::v1alpha1::PermissionRule; + +PermissionRuleImpl::PermissionRuleImpl() : ScalarProtoMessageWrapper<ProtoPermissionRule>() {} + +PermissionRuleImpl::PermissionRuleImpl(ProtoPermissionRule *rule) + : ScalarProtoMessageWrapper<ProtoPermissionRule>(rule) {} + +PermissionRuleImpl::PermissionRuleImpl(std::string permission, bool isGrant, bool isPriority) + : PermissionRuleImpl() { + 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 & { + 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 -> const bool { + return this->impl->wrapped->grant(); +} + +auto PermissionRule::SetGrant(bool isGrant) -> void { this->impl->wrapped->set_grant(isGrant); } + +[[nodiscard]] auto PermissionRule::IsPriority() const -> 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 { + return this->GetPermission() == other.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..57cacee709b3cd9d6ed423b2513510ace491d033 --- /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..9294de142e8989078d82c334d0c021b07572e96c --- /dev/null +++ b/src/caosdb/acm/role.cpp @@ -0,0 +1,113 @@ +/* + * 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 <utility> // for move +#include <vector> // for vector + +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(std::string name, std::string description) + : ScalarProtoMessageWrapper<ProtoRole>() { + 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(std::string name) : RoleImpl(std::move(name), "") {} + +Role::Role(RoleImpl *wrapped) : wrapped(std::move(wrapped)) {} + +Role::Role(std::string name, std::string description) + : wrapped(new RoleImpl(std::move(name), std::move(description))) {} + +Role::Role(std::string name) : Role(std::move(name), {""}) {} + +Role::Role(const Role &role) : Role(role.GetName(), role.GetDescription()) {} + +auto Role::operator=(const Role &role) -> Role & { + this->wrapped->wrapped->CopyFrom(*(role.wrapped->wrapped)); + + return *this; +} + +Role::~Role() { delete this->wrapped; } +auto Role::GetName() const -> const std::string & { return this->wrapped->wrapped->name(); } + +auto Role::SetName(std::string name) -> void { + if (!name.empty()) { + this->wrapped->wrapped->set_name(std::move(name)); + } else { + this->wrapped->wrapped->clear_name(); + } +} + +auto Role::GetDescription() const -> const std::string & { + return this->wrapped->wrapped->description(); +} + +auto Role::SetDescription(std::string description) -> void { + if (!description.empty()) { + this->wrapped->wrapped->set_description(std::move(description)); + } else { + this->wrapped->wrapped->clear_description(); + } +} + +auto Role::ToString() const -> std::string { return this->wrapped->ToString(); } + +[[nodiscard]] auto Role::GetPermissionRules() const -> const PermissionRules & { + return this->wrapped->rules; +} + +auto Role::Grant(std::string permission, bool isPriority) -> void { + this->wrapped->rules.emplace(std::move(permission), true, isPriority); +} + +auto Role::RevokeGrant(std::string permission, bool isPriority) -> void { + auto rule = this->wrapped->rules.begin(); + while (rule != this->wrapped->rules.end()) { + if (rule->IsGrant() && rule->IsPriority() == isPriority && + rule->GetPermission() == permission) { + this->wrapped->rules.erase(rule); + return; + } else { + rule++; + } + } +} +} // 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..b30e6e11faa248f5babfeb9234dc276aaccad9b4 --- /dev/null +++ b/src/caosdb/acm/role_impl.h @@ -0,0 +1,64 @@ +/* + * 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 <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(std::string name); + /** + * Constructor. Instanciate a role with the given name and description. + */ + RoleImpl(std::string name, 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: + PermissionRules rules; +}; + +} // namespace caosdb::acm diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index 02c190ee1beff00fc3cbffa950628a67b5ffa32d..6632933a7787a2ba1284f0cdb00d2c8a478bcdba 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -20,6 +20,9 @@ * */ #include "caosdb/connection.h" +#include "caosdb/acm/role_impl.h" // for RoleImpl +#include "caosdb/acm/v1alpha1/main.grpc.pb.h" // for AccessControlMan... +#include "caosdb/acm/v1alpha1/main.pb.h" // for ListRolesRequest #include "caosdb/configuration.h" // for ConnectionConfigur... #include "caosdb/exceptions.h" // for ConfigurationError #include "caosdb/info.h" // for VersionInfo @@ -27,13 +30,23 @@ #include "caosdb/info/v1/main.pb.h" // for GetVersionInfoRequest #include "caosdb/transaction.h" // for Transaction #include "caosdb/transaction_status.h" // for TransactionStatus -#include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH... #include <grpcpp/create_channel.h> // for CreateChannel #include <grpcpp/impl/codegen/client_context.h> // for ClientContext #include <grpcpp/impl/codegen/status.h> // for Status +#include <grpcpp/impl/codegen/status_code_enum.h> // for StatusCode, UNAUTH... #include <string> // for string, operator+ namespace caosdb::connection { +using caosdb::acm::RoleImpl; +using caosdb::acm::v1alpha1::AccessControlManagementService; +using caosdb::acm::v1alpha1::CreateSingleRoleRequest; +using caosdb::acm::v1alpha1::CreateSingleRoleResponse; +using caosdb::acm::v1alpha1::DeleteSingleRoleRequest; +using caosdb::acm::v1alpha1::DeleteSingleRoleResponse; +using caosdb::acm::v1alpha1::ListRolesRequest; +using caosdb::acm::v1alpha1::ListRolesResponse; +using caosdb::acm::v1alpha1::RetrieveSingleRoleRequest; +using caosdb::acm::v1alpha1::RetrieveSingleRoleResponse; using caosdb::configuration::ConfigurationManager; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1::EntityTransactionService; @@ -53,6 +66,8 @@ Connection::Connection(const ConnectionConfiguration &configuration) { this->entity_transaction_service = std::make_shared<EntityTransactionService::Stub>(this->channel); this->file_transmission_service = std::make_shared<FileTransmissionService::Stub>(this->channel); + this->access_controll_management_service = + std::make_unique<AccessControlManagementService::Stub>(this->channel); } auto Connection::RetrieveVersionInfoNoExceptions() const noexcept -> TransactionStatus { @@ -97,6 +112,121 @@ auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { return std::make_unique<Transaction>(entity_service, file_service); } +[[nodiscard]] auto Connection::RetrieveSingleRole(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(new RoleImpl(role)); +} + +auto Connection::DeleteSingleRole(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(); + 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: + 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 *roles = response.mutable_roles(); + for (auto &role_item : *roles) { + auto *implementation = new RoleImpl(role_item); + result.emplace_back(implementation); + } + return result; +} + auto ConnectionManager::mHasConnection(const std::string &name) const -> bool { auto it = connections.find(name); return it != connections.end(); diff --git a/src/cxxcaosdbcli.cpp b/src/cxxcaosdbcli.cpp index e6e5cee66e6c90ab7719434d7667cd6ad20ccbb8..bfa48b8f6baa460fef512b957b9f8da4a6eed90f 100644 --- a/src/cxxcaosdbcli.cpp +++ b/src/cxxcaosdbcli.cpp @@ -29,59 +29,145 @@ #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 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::parse_command_line; +using boost::program_options::positional_options_description; +using boost::program_options::store; +using boost::program_options::value; +using boost::program_options::variables_map; +using caosdb::acm::Role; - 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(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(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; + } +} + +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(std::string name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + auto role = connection->RetrieveSingleRole(name); + std::cout << role.ToString() << std::endl; +} + +auto create_role(std::string name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + Role role(name); + connection->CreateSingleRole(role); + std::cout << "OK" << std::endl; +} + +auto delete_role(std::string name) { + const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + connection->DeleteSingleRole(name); + std::cout << "OK" << std::endl; +} + +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")("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"); - // 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")) { + std::cout << desc << std::endl; + } else if (vm["version"].as<bool>()) { + } else if (vm.count("test-connection")) { + test_connection(); + } else if (vm.count("list-roles")) { + list_roles(); + } else if (vm.count("retrieve-role")) { + } else if (vm.count("retrieve")) { + } else if (vm.count("create-role")) { + } else if (vm.count("delete-role")) { + } else if (vm.count("execute-query")) { + } else { + print_version(true); + test_connection(); } return 0; @@ -92,7 +178,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 12741c41d4e31e12548711f14dc716b4eb7f65c8..4587a64f96921988b15e8d79cf0eb9389c537fa3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ set(test_cases test_issues test_list_properties test_protobuf + test_role test_transaction test_utility test_value @@ -41,7 +42,7 @@ set(test_cases # special linting for tests set(_CMAKE_CXX_CLANG_TIDY_TEST_CHECKS - "${_CMAKE_CXX_CLANG_TIDY_CHECKS},-cert-err58-cpp,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-owning-memory,-modernize-use-trailing-return-type,-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-avoid-goto,-hicpp-avoid-goto,-readability-function-cognitive-complexity,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-clang-analyzer-cplusplus.Move" + "${_CMAKE_CXX_CLANG_TIDY_CHECKS},-cert-err58-cpp,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-owning-memory,-modernize-use-trailing-return-type,-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-avoid-goto,-hicpp-avoid-goto,-readability-function-cognitive-complexity,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-clang-analyzer-cplusplus.Move,-clang-diagnostic-unused-result" ) # add special cmake functions for gtest diff --git a/test/test_connection.cpp b/test/test_connection.cpp index 00c3bcf58e605e112a404fdfe6ece62e829562ea..9b5544e69313c03d4d657628de432a3eaea48100 100644 --- a/test/test_connection.cpp +++ b/test/test_connection.cpp @@ -87,4 +87,11 @@ TEST_F(test_connection, connection_manager_get_connection) { EXPECT_TRUE(ConnectionManager::GetConnection("local-caosdb-admin")); } +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."); +} + } // namespace caosdb::connection diff --git a/test/test_data_type.cpp b/test/test_data_type.cpp index 02861d3baabbab2aee6fead973245029379db662..6b7b8b6ea3d36135164d73c90e8f45693a49b1cc 100644 --- a/test/test_data_type.cpp +++ b/test/test_data_type.cpp @@ -20,17 +20,17 @@ * */ -#include "caosdb/data_type.h" // for DataType, AtomicDataType -#include "caosdb/entity.h" // for Entity -#include "caosdb/entity/v1/main.pb.h" // for DataType, Ato... -#include "caosdb/logging.h" // for CAOSDB_LOG_DEBUG -#include "caosdb/protobuf_helper.h" // for CAOSDB_DEBUG_... -#include <gtest/gtest-message.h> // for Message -#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApi... -#include <gtest/gtest_pred_impl.h> // for AssertionResult, Test -#include <map> // for map, operator!= -#include <string> // for allocator -#include <utility> // for pair +#include "caosdb/data_type.h" // for DataType, AtomicDataType +#include "caosdb/entity.h" // for Entity +#include "caosdb/entity/v1/main.pb.h" // for DataType, Ato... +#include "caosdb/logging.h" // for CAOSDB_LOG_DEBUG +#include "caosdb/protobuf_helper.h" // for CAOSDB_DEBUG_... +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApi... +#include <gtest/gtest_pred_impl.h> // for AssertionResult, Test +#include <map> // for map, operator!= +#include <string> // for allocator +#include <utility> // for pair namespace caosdb::entity { using ProtoEntity = caosdb::entity::v1::Entity; diff --git a/test/test_role.cpp b/test/test_role.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b3e68451dfa2bddecc9a88ab174866d2644a0e1 --- /dev/null +++ b/test/test_role.cpp @@ -0,0 +1,70 @@ +/* + * + * 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 <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 <memory> // for allocator + +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, get_permission_rules) { + auto role1 = Role("role1", "description1"); + EXPECT_TRUE(role1.GetPermissionRules().empty()); + + role1.Grant("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + + role1.Grant("SOME_OTHER_PERMISSION", true); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + + role1.RevokeGrant("SOME_PERMISSION", false); + EXPECT_FALSE(role1.GetPermissionRules().empty()); + + role1.RevokeGrant("SOME_OTHER_PERMISSION", true); + EXPECT_TRUE(role1.GetPermissionRules().empty()); +} + +} // namespace caosdb::acm