diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff1db6df2ede12b6254e3c9ad1841fe1a3603061..f437abb0578ca69685857826a8b34343c0a0b89c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -84,7 +84,8 @@ test: script: - mkdir build - cd build - - conan install .. -s "compiler.libcxx=libstdc++11" + - VERSION="$(conan inspect --raw version ..)" + - conan install -s "compiler.libcxx=libstdc++11" -o build_acm=True .. "caosdb/$VERSION@_/_" - cmake -DCMAKE_BUILD_TYPE=Debug .. - cmake --build . -j - cmake --build . -j --target unit_test_coverage diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ee6d193f6814d1d24eb8cb35e3658a87d00e601..70cae6f2b55e414a4dc0ae867d715a4eaef740fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,11 @@ IF (WIN32) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) ENDIF() +IF (BUILD_ACM) + message(STATUS "BUILD_ACM") + add_compile_definitions("BUILD_ACM") +ENDIF() + ########################################### ### DEPENDENCY MANAGEMENT with CONAN ########################################### @@ -83,6 +88,12 @@ set(PROTO_FILES ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/entity/v1/main.proto ) +IF (BUILD_ACM) + list(APPEND PROTO_FILES + ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/acm/v1alpha1/main.proto + ) +ENDIF() + set(PROTO_PATH ${PROJECT_SOURCE_DIR}/proto/proto) # compiler binaries @@ -173,6 +184,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 @@ -231,12 +243,6 @@ target_link_libraries(cxxcaosdbcli ### LINTING with CLANG-TIDY and INCLUDE-WHAT-YOU-USE ####################################################### -########################################### -### PARANOID COMPILER SETTINGS -########################################### -option(PARANOID_COMPILER_SETTINGS "Enable extra-paranoid compiler settings -(which may even flag errors for code in the dependencies. These only apply in -Debug BUILD_TYPE with SKIP_LINTING=Off or when LINTING=On." OFF) include(CheckCXXCompilerFlag) include(CheckCCompilerFlag) diff --git a/Makefile b/Makefile index 544c8039b4f27ccef8b379d1009bfe9173666bc3..21cd43348cb545bfbf0acd8985dd0e3a459ca8f1 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,10 @@ endif .PHONY: help help: @echo "Targets:" - @echo " conan-install - Install locally with Conan." @echo " style - auto-format the source files." + @echo " conan-install - Install locally with Conan." + @echo -e " conan-create - Create conan binary package in the local conan\n"\ + " repostory." style: $(CLANG_FORMAT) -i --verbose \ @@ -65,11 +67,12 @@ conan-install-debug: .PHONY: conan-install-debug conan-create: - conan create . -s $(CONAN_SETTINGS) + conan create -s $(CONAN_SETTINGS) -o caosdb:build_acm=True . "caosdb/$$(conan inspect --raw version .)@_/_" .PHONY: conan-create conan-create-debug: - conan create . -s $(CONAN_SETTINGS) -s build_type=Debug + conan create -s $(CONAN_SETTINGS) -s build_type=Debug -o caosdb:build_acm=True . "caosdb/$$(conan inspect --raw version .)@_/_" + .PHONY: conan-create-debug conan: conan-install conan-create diff --git a/conanfile.py b/conanfile.py index c91038d2d9ab6e2f23c21696bd62d19c0421ca78..cda65824eb827c331d33ae1e57e8430b6ff972ee 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,8 +10,16 @@ class CaosdbConan(ConanFile): description = "C++ library for the CaosDB project" topics = ("data management", "caosdb") settings = "os", "compiler", "build_type", "arch" - options = {"shared": [True, False], "fPIC": [True, False]} - default_options = {"shared": False, "fPIC": True} + options = { + "shared": [True, False], + "fPIC": [True, False], + "build_acm": [True, False], + } + default_options = { + "shared": False, + "fPIC": True, + "build_acm": False, + } generators = "cmake" requires = [ ("grpc/1.45.2"), @@ -41,14 +49,12 @@ class CaosdbConan(ConanFile): def build(self): cmake = CMake(self) + if self.options.build_acm: + cmake.definitions["BUILD_ACM"] = "On" + cmake.configure(source_folder="") cmake.build() - # Explicit way: - # self.run('cmake %s/hello %s' - # % (self.source_folder, cmake.command_line)) - # self.run("cmake --build . %s" % cmake.build_config) - def package(self): self.copy("*.h", dst="include", src="include") self.copy("*.dll", dst="bin", keep_path=False) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index adbf07c79763f4af236ac90eff74e44669ec7f02..be4e73f31904decf166a523c229b58a0ca47af10 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Simple `User` class and methods for user creation/retrieval/deletion. + ### Changed * Transaction::ExecuteAsynchronously is actually asynchronous now. diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 784e2e04ae3baa8aacffbacbba2ecc55c1c0af6a..29c27eaf536434d2f870fc111ca00f5febec1327 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -50,6 +50,12 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_error.h ) +IF(BUILD_ACM) + list(APPEND libcaosdb_INCL + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user.h + ) +ENDIF() + # pass variable to parent scope set(libcaosdb_INCL ${libcaosdb_INCL} PARENT_SCOPE) diff --git a/include/caosdb/acm/user.h b/include/caosdb/acm/user.h new file mode 100644 index 0000000000000000000000000000000000000000..9c1ae91ec9451c7d7a33b4b209646676fa2d7eb0 --- /dev/null +++ b/include/caosdb/acm/user.h @@ -0,0 +1,138 @@ +/* + * 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 Users, together with roles, and permissions are a fundamental concept + * of the access controll management of CaosDB. + * + * @file caosdb/acm/user.h + * @author Timm Fitchen + * @date 2022-06-29 + */ +#ifdef BUILD_ACM +#ifndef CAOSDB_ACM_USER_H +#define CAOSDB_ACM_USER_H + +#include <memory> // for unique_ptr +#include <string> // for string + +namespace caosdb::connection { +class Connection; +} + +namespace caosdb::acm { + +/** + * The UserImpl class is the delegate of the User class. It hides the + * implementation details from the clients. + */ +class UserImpl; + +/** + * The User class is a delegator. The actual data is stored in a wrapped + * UserImpl object. + */ +class User { +public: + /** + * Default constructor. + * + * Initialize a user without any name, realm, password... + */ + User(); + /** + * Constructor. Initialize a user in the given realm with the given name. + */ + explicit User(std::string realm, std::string name); + /** + * Constructor. Initialize a user with the given name. + */ + explicit User(std::string name); + /** + * Copy constructor. + */ + User(const User &user); + /** + * Move constructor. + * + * The moved-from user is empty, but still usable. + */ + User(User &&user) noexcept; + /** + * Copy assignment. + */ + auto operator=(const User &user) -> User &; + /** + * Move assignment. + * + * The moved-from user is empty, but still usable. + */ + auto operator=(User &&user) noexcept -> User &; + /** + * Dtor. + */ + ~User(); + + /** + * Return a string representation of this user. + */ + auto ToString() const -> std::string; + /** + * Return the name of this user or the empty string. + */ + [[nodiscard]] auto GetName() const -> const std::string &; + /** + * Set the name of this user. + */ + auto SetName(const std::string &name) -> void; + /** + * Return the realm of this user or the empty. + */ + [[nodiscard]] auto GetRealm() const -> const std::string &; + /** + * Set the realm of this user. + */ + auto SetRealm(const std::string &realm) -> void; + /** + * Return the password of this user or the empty string. + */ + [[nodiscard]] auto GetPassword() const -> const std::string &; + /** + * Set the password of this user. + */ + auto SetPassword(const std::string &password) -> void; + + friend class caosdb::connection::Connection; + +private: + /** + * Constructor. Create a user object from the given UserImpl delegate. + */ + explicit User(std::unique_ptr<UserImpl> wrapped); + /** + * The UserImpl delegate. + */ + std::unique_ptr<UserImpl> wrapped; +}; + +} // namespace caosdb::acm +#endif +#endif diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index ac1d05a419816ac21bd0a70ceafaf97428bd08da..d0414f2e84f81fe9e86a50f2e6411788cf0befeb 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -27,6 +27,10 @@ * @date 2021-05-18 * @brief Configuration and setup of the connection. */ +#ifdef BUILD_ACM +#include "caosdb/acm/user.h" // for User +#include "caosdb/acm/v1alpha1/main.grpc.pb.h" // for AccessControlMan... +#endif #include "caosdb/authentication.h" // for Authenticator #include "caosdb/configuration.h" // for ConnectionConfigura... #include "caosdb/entity/v1/main.grpc.pb.h" // for EntityTransactionSe... @@ -41,6 +45,10 @@ #include <string> // for string, basic_string namespace caosdb::connection { +#ifdef BUILD_ACM +using caosdb::acm::User; +using caosdb::acm::v1alpha1::AccessControlManagementService; +#endif using caosdb::authentication::Authenticator; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1::EntityTransactionService; @@ -89,8 +97,33 @@ 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>; +#ifdef BUILD_ACM + /** + * Retrieve a single user. + */ + // TODO(tf) find a way to deal with this: + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + [[nodiscard]] auto RetrieveSingleUser(const std::string &realm, const std::string &name) const + -> User; + + /** + * Create a new user. + */ + auto CreateSingleUser(const User &user) const -> void; + + /** + * Delete an existing user. + */ + // TODO(tf) find a way to deal with this: + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + auto DeleteSingleUser(const std::string &realm, const std::string &name) const -> void; +#endif + private: /// GRPC-Channel (HTTP/2 Connection plus Authentication). We use a shared /// pointer because Transaction instances also own the channel. @@ -107,6 +140,12 @@ 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; +#ifdef BUILD_ACM + /// 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; +#endif }; /** diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3654e01f5aa6f184338ff74070755df87f9147c..f64bc7671cbe142390ca5439916dfbddd3660294 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,5 +39,12 @@ set(libcaosdb_SRC ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/status_code_description.cpp ) +IF(BUILD_ACM) + list(APPEND libcaosdb_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/acm/user_impl.h + ) +ENDIF() + # pass variable to parent scope set(libcaosdb_SRC ${libcaosdb_SRC} PARENT_SCOPE) diff --git a/src/caosdb/acm/user.cpp b/src/caosdb/acm/user.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2988287997c723c35bdd1f470572c4dc5797879f --- /dev/null +++ b/src/caosdb/acm/user.cpp @@ -0,0 +1,116 @@ +/* + * 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/user.h" +#include "caosdb/acm/user_impl.h" // for UserImpl +#include "caosdb/acm/v1alpha1/main.pb.h" // for ProtoUser +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoUser = caosdb::acm::v1alpha1::User; + +UserImpl::UserImpl() = default; + +UserImpl::UserImpl(std::string realm, std::string name) { + if (!name.empty()) { + this->wrapped->set_name(name); + } + if (!realm.empty()) { + this->wrapped->set_realm(realm); + } +} + +UserImpl::UserImpl(ProtoUser *user) : ScalarProtoMessageWrapper<ProtoUser>(user) {} + +UserImpl::UserImpl(std::string name) : UserImpl("", std::move(name)) {} + +User::User() : wrapped(std::make_unique<UserImpl>()) {} + +User::User(std::unique_ptr<UserImpl> wrapped) : wrapped(std::move(wrapped)) {} + +User::User(std::string realm, std::string name) + : wrapped(std::make_unique<UserImpl>(std::move(realm), std::move(name))) {} + +User::User(std::string name) : User({""}, std::move(name)) {} + +User::User(const User &user) : User() { + this->wrapped->wrapped->CopyFrom(*(user.wrapped->wrapped)); +} + +User::User(User &&user) noexcept : wrapped(std::move(user.wrapped)) { + user.wrapped = std::make_unique<UserImpl>(); +} + +auto User::operator=(const User &user) -> User & { + if (this == &user) { + return *this; + } + this->wrapped->wrapped->CopyFrom(*(user.wrapped->wrapped)); + + return *this; +} + +auto User::operator=(User &&user) noexcept -> User & { + if (this == &user) { + return *this; + } + this->wrapped = std::move(user.wrapped); + user.wrapped = std::make_unique<UserImpl>(); + + return *this; +} + +User::~User() = default; + +auto User::GetName() const -> const std::string & { return this->wrapped->wrapped->name(); } + +auto User::SetName(const std::string &name) -> void { + if (!name.empty()) { + this->wrapped->wrapped->set_name(name); + } else { + this->wrapped->wrapped->clear_name(); + } +} + +auto User::GetRealm() const -> const std::string & { return this->wrapped->wrapped->realm(); } + +auto User::SetRealm(const std::string &realm) -> void { + if (!realm.empty()) { + this->wrapped->wrapped->set_realm(realm); + } else { + this->wrapped->wrapped->clear_realm(); + } +} + +auto User::GetPassword() const -> const std::string & { return this->wrapped->password; } + +auto User::SetPassword(const std::string &password) -> void { + if (!password.empty()) { + this->wrapped->password = password; + } else { + this->wrapped->password.clear(); + } +} + +auto User::ToString() const -> std::string { return this->wrapped->ToString(); } + +} // namespace caosdb::acm diff --git a/src/caosdb/acm/user_impl.h b/src/caosdb/acm/user_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..ea2e0da6738942f922d158f3adfa03d81c0c862a --- /dev/null +++ b/src/caosdb/acm/user_impl.h @@ -0,0 +1,58 @@ +/* + * 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/user.h" +#include "caosdb/acm/v1alpha1/main.pb.h" // for ProtoUser +#include "caosdb/protobuf_helper.h" // for ProtoMessageWrapper +#include <memory> // for unique_ptr +#include <utility> // for move + +namespace caosdb::acm { +using caosdb::utility::ScalarProtoMessageWrapper; +using ProtoUser = caosdb::acm::v1alpha1::User; + +/** + * UserImpl class is designed to hide the implementation which makes direct use + * of the protobuf objects underneath from the clients of the caosdb library. + */ +class UserImpl : public ScalarProtoMessageWrapper<ProtoUser> { +public: + UserImpl(); + /** + * Constructor. Instanciate a user with the given name. + */ + explicit UserImpl(std::string name); + /** + * Constructor. Instanciate a user with the given realm and name. + */ + UserImpl(std::string realm, std::string name); + /** + * Constructor. Instanciate a user from the server's responces. + */ + UserImpl(ProtoUser *user); + + friend class User; + friend class caosdb::connection::Connection; + +private: + std::string password; +}; + +} // namespace caosdb::acm diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index 867c38a9be90a127a84c5b3e104497535cf7ec3d..abc87dacb054a570540eff7e896eb1058b7b267b 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -20,6 +20,11 @@ * */ #include "caosdb/connection.h" +#ifdef BUILD_ACM +#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... +#endif #include "caosdb/configuration.h" // for ConnectionConfigur... #include "caosdb/exceptions.h" // for ConfigurationError #include "caosdb/info.h" // for VersionInfo @@ -32,8 +37,19 @@ #include <grpcpp/support/status.h> // for Status #include <grpcpp/support/status_code_enum.h> // for StatusCode, UNAUTHENTIC... #include <string> // for string, operator+ +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" namespace caosdb::connection { +#ifdef BUILD_ACM +using caosdb::acm::UserImpl; +using caosdb::acm::v1alpha1::AccessControlManagementService; +using caosdb::acm::v1alpha1::CreateSingleUserRequest; +using caosdb::acm::v1alpha1::CreateSingleUserResponse; +using caosdb::acm::v1alpha1::DeleteSingleUserRequest; +using caosdb::acm::v1alpha1::DeleteSingleUserResponse; +using caosdb::acm::v1alpha1::RetrieveSingleUserRequest; +using caosdb::acm::v1alpha1::RetrieveSingleUserResponse; +#endif using caosdb::configuration::ConfigurationManager; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1::EntityTransactionService; @@ -53,6 +69,10 @@ 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); +#ifdef BUILD_ACM + this->access_controll_management_service = + std::make_unique<AccessControlManagementService::Stub>(this->channel); +#endif } auto Connection::RetrieveVersionInfoNoExceptions() const noexcept -> TransactionStatus { @@ -97,6 +117,96 @@ auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { return std::make_unique<Transaction>(entity_service, file_service); } +#ifdef BUILD_ACM +// TODO(tf) find a way to deal with this: +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +[[nodiscard]] auto Connection::RetrieveSingleUser(const std::string &realm, + const std::string &name) const -> User { + RetrieveSingleUserRequest request; + request.set_name(name); + request.set_realm(realm); + RetrieveSingleUserResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->RetrieveSingleUser(&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 *user = response.release_user(); + return User(std::make_unique<UserImpl>(user)); +} + +// TODO(tf) find a way to deal with this: +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +auto Connection::DeleteSingleUser(const std::string &realm, const std::string &name) const -> void { + DeleteSingleUserRequest request; + request.set_name(name); + request.set_realm(realm); + DeleteSingleUserResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->DeleteSingleUser(&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 Connection::CreateSingleUser(const User &user) const -> void { + CreateSingleUserRequest request; + request.set_allocated_user(user.wrapped->wrapped); + request.mutable_password_setting()->set_password(user.GetPassword()); + CreateSingleUserResponse response; + grpc::ClientContext context; + const grpc::Status grpc_status = + this->access_controll_management_service->CreateSingleUser(&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(); +} +#endif + auto ConnectionManager::mHasConnection(const std::string &name) const -> bool { auto it = connections.find(name); return it != connections.end(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 12741c41d4e31e12548711f14dc716b4eb7f65c8..659254bf1e215c6c203eb82c0d2bca030a12a0b9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,13 +35,19 @@ set(test_cases test_ccaosdb ) +IF(BUILD_ACM) + list(APPEND test_cases + test_user + ) +ENDIF() + ################################################### ### Set up tests using GoogleTest (GTest) ################################################### # 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..9b8783a0ae88f6669de4e5ec4e5a43dea26447c5 100644 --- a/test/test_connection.cpp +++ b/test/test_connection.cpp @@ -19,6 +19,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ +#ifdef BUILD_ACM +#include "caosdb/acm/user.h" // for User +#endif #include "caosdb/certificate_provider.h" // for PemCertificateProvider #include "caosdb/configuration.h" // for InsecureConnectionConfigura... #include "caosdb/connection.h" // for ConnectionManager @@ -31,6 +34,9 @@ #include <string> // for operator+, string namespace caosdb::connection { +#ifdef BUILD_ACM +using caosdb::acm::User; +#endif using caosdb::configuration::ConfigurationManager; using caosdb::configuration::InsecureConnectionConfiguration; using caosdb::configuration::PemCertificateProvider; @@ -87,4 +93,30 @@ TEST_F(test_connection, connection_manager_get_connection) { EXPECT_TRUE(ConnectionManager::GetConnection("local-caosdb-admin")); } +#ifdef BUILD_ACM +TEST_F(test_connection, test_create_single_user) { + auto connection = ConnectionManager::GetDefaultConnection(); + User user; + EXPECT_THROW_MESSAGE(connection->CreateSingleUser(user), caosdb::exceptions::ConnectionError, + "The attempt to execute this transaction was not successful because the " + "connection to the server could not be established."); +} + +TEST_F(test_connection, test_delete_single_user) { + auto connection = ConnectionManager::GetDefaultConnection(); + EXPECT_THROW_MESSAGE(connection->DeleteSingleUser("realm", "user"), + caosdb::exceptions::ConnectionError, + "The attempt to execute this transaction was not successful because the " + "connection to the server could not be established."); +} + +TEST_F(test_connection, test_retrieve_single_user) { + auto connection = ConnectionManager::GetDefaultConnection(); + EXPECT_THROW_MESSAGE(auto results = connection->RetrieveSingleUser("realm", "user"), + 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_user.cpp b/test/test_user.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5715833d0f23c5e8b887a8198fe5f96324dcafd8 --- /dev/null +++ b/test/test_user.cpp @@ -0,0 +1,120 @@ +/* + * + * 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/user.h" // for User +#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 <utility> // for move + +namespace caosdb::acm { + +TEST(test_user, user_name) { + auto user = User("user1"); + EXPECT_EQ(user.GetName(), "user1"); + user.SetName("user2"); + EXPECT_EQ(user.GetName(), "user2"); +} + +TEST(test_user, user_realm) { + auto user1 = User("realm1", "user1"); + EXPECT_EQ(user1.GetName(), "user1"); + EXPECT_EQ(user1.GetRealm(), "realm1"); + user1.SetName("user3"); + EXPECT_EQ(user1.GetRealm(), "realm1"); + + auto user2 = User("realm1", "user2"); + EXPECT_EQ(user2.GetRealm(), "realm1"); + EXPECT_EQ(user2.GetName(), "user2"); + user2.SetRealm("realm2"); + EXPECT_EQ(user2.GetRealm(), "realm2"); + EXPECT_EQ(user2.GetName(), "user2"); +} + +TEST(test_user, user_password) { + auto user1 = User("realm1", "user1"); + user1.SetPassword("1234"); + EXPECT_EQ(user1.GetName(), "user1"); + EXPECT_EQ(user1.GetPassword(), "1234"); + user1.SetName("user3"); + EXPECT_EQ(user1.GetPassword(), "1234"); + + auto user2 = User("realm1", "user2"); + user2.SetPassword("1234"); + EXPECT_EQ(user2.GetPassword(), "1234"); + EXPECT_EQ(user2.GetName(), "user2"); + user2.SetPassword("abcd"); + EXPECT_EQ(user2.GetPassword(), "abcd"); + EXPECT_EQ(user2.GetName(), "user2"); +} + +TEST(test_user, test_copy_constructor) { + const auto user1 = User("user1"); + User user2(user1); + + EXPECT_EQ(user2.GetName(), "user1"); + EXPECT_EQ(user1.GetName(), "user1"); + + user2.SetName("user2"); + + EXPECT_EQ(user2.GetName(), "user2"); + EXPECT_EQ(user1.GetName(), "user1"); +} + +TEST(test_user, test_copy_assign) { + const auto user1 = User("user1"); + auto user2 = user1; + + EXPECT_EQ(user2.GetName(), "user1"); + user2.SetName("user2"); + EXPECT_EQ(user2.GetName(), "user2"); + EXPECT_EQ(user1.GetName(), "user1"); +} + +TEST(test_user, test_move_constructor) { + auto user1 = User("user1"); + User user2(std::move(user1)); + + EXPECT_EQ(user2.GetName(), "user1"); + // NOLINTNEXTLINE + EXPECT_EQ(user1.GetName(), ""); +} + +TEST(test_user, test_move_assign) { + auto user1 = User("user2"); + user1.SetPassword("1234"); + EXPECT_EQ(user1.GetName(), "user2"); + EXPECT_EQ(user1.GetPassword(), "1234"); + + User user2; + user2 = std::move(user1); + + EXPECT_EQ(user2.GetName(), "user2"); + EXPECT_EQ(user2.GetPassword(), "1234"); + // NOLINTNEXTLINE + EXPECT_EQ(user1.GetName(), ""); + // NOLINTNEXTLINE + EXPECT_EQ(user1.GetPassword(), ""); +} + +} // namespace caosdb::acm