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