From 8b43244d46fbfd49b730b6184753cb6e3cb0e501 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Fri, 18 Feb 2022 13:19:51 +0100
Subject: [PATCH] WIP: acm

---
 CMakeLists.txt                        |   2 +
 include/CMakeLists.txt                |   1 +
 include/caosdb/acm/permission_rule.h  |  67 ++++++++++
 include/caosdb/acm/role.h             |  74 +++++++++++
 include/caosdb/connection.h           |  47 +++++--
 include/caosdb/entity.h               |   2 +-
 src/CMakeLists.txt                    |   3 +
 src/caosdb/acm/permission_rule.cpp    |  82 ++++++++++++
 src/caosdb/acm/permission_rule_impl.h |  51 ++++++++
 src/caosdb/acm/role.cpp               | 113 +++++++++++++++++
 src/caosdb/acm/role_impl.h            |  64 ++++++++++
 src/caosdb/connection.cpp             | 132 ++++++++++++++++++-
 src/cxxcaosdbcli.cpp                  | 174 +++++++++++++++++++-------
 test/CMakeLists.txt                   |   3 +-
 test/test_connection.cpp              |   7 ++
 test/test_data_type.cpp               |  22 ++--
 test/test_role.cpp                    |  70 +++++++++++
 17 files changed, 844 insertions(+), 70 deletions(-)
 create mode 100644 include/caosdb/acm/permission_rule.h
 create mode 100644 include/caosdb/acm/role.h
 create mode 100644 src/caosdb/acm/permission_rule.cpp
 create mode 100644 src/caosdb/acm/permission_rule_impl.h
 create mode 100644 src/caosdb/acm/role.cpp
 create mode 100644 src/caosdb/acm/role_impl.h
 create mode 100644 test/test_role.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8ff3f9a..8d762b3 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 784e2e0..094b02a 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 0000000..0ea1121
--- /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 0000000..feb2138
--- /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 ac1d05a..1a833a1 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 32722a8..52a69e0 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 a3654e0..56269b9 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 0000000..8ce2197
--- /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 0000000..57cacee
--- /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 0000000..9294de1
--- /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 0000000..b30e6e1
--- /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 02c190e..6632933 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 e6e5cee..bfa48b8 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 12741c4..4587a64 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 00c3bcf..9b5544e 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 02861d3..6b7b8b6 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 0000000..8b3e684
--- /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
-- 
GitLab