diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json
index 99be5195d410b5b78f99d119b781df093d43425d..c3b1302374829703fd21ad3adc553d65ac8d9bf2 100644
--- a/dbrepo-authentication-service/dbrepo-realm.json
+++ b/dbrepo-authentication-service/dbrepo-realm.json
@@ -120,6 +120,17 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "e63e61a2-d852-4ad3-bfb5-92d9ceafef6a",
+      "name" : "escalated-user-handling",
+      "description" : "${escalated-user-handling}",
+      "composite" : true,
+      "composites" : {
+        "realm" : [ "find-user" ]
+      },
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "be4e1aba-e276-4241-b6ea-01dce6c52f8b",
       "name" : "find-container",
@@ -206,6 +217,14 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "993b5c69-9eb2-42af-ac28-b4a46c6b61f2",
+      "name" : "find-user",
+      "description" : "${find-user}",
+      "composite" : false,
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "e4cfdc4d-2373-477b-a8df-161db99aba00",
       "name" : "create-foreign-identifier",
@@ -1880,7 +1899,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -1889,7 +1908,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -1941,7 +1960,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "e29d33f1-c44c-4f6b-b8ca-54eb4b468f1d",
+    "id" : "85d85037-1863-4869-b9ab-09582853f779",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -1963,7 +1982,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "bc4badef-d637-4448-b345-3e79f24290b0",
+    "id" : "e89d11ed-c578-409a-aaed-d00db2951f66",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -1992,7 +2011,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "06ddc635-bb0d-443a-ab6b-7f56be7fd59a",
+    "id" : "5bfb765d-30bf-4708-b85e-01beb0813a75",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2014,7 +2033,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a8230646-a4a8-40c2-a37a-e7ac4daf9a67",
+    "id" : "dfe729c0-df4b-4b9b-8170-fd29d703f691",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2036,7 +2055,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e84a6624-232f-401e-9937-6c5865b2a341",
+    "id" : "e52f6f8c-edd8-42eb-a956-1e642d054a09",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -2058,7 +2077,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "00094f7f-cdcb-4670-87b0-d75ed3fa986b",
+    "id" : "0fcdfbb3-4e4f-4c55-9e06-9baf3afef314",
     "alias" : "Handle Existing Account",
     "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
     "providerId" : "basic-flow",
@@ -2080,7 +2099,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e8cea56d-d07c-4696-b389-0f70449ba26d",
+    "id" : "a3db4f3a-a772-4530-b948-c2ea688dc993",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -2102,7 +2121,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "505a65be-a4f8-4e6e-a1a0-21d20d3f07a5",
+    "id" : "d540d238-e69c-4eb1-8238-bf43c9f59118",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -2125,7 +2144,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "9c31dad3-3367-4f7e-9fd0-3c855e1da0b8",
+    "id" : "b0b433e3-3a58-4915-8833-ad55fef4aab7",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -2147,7 +2166,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e6fdd307-ca87-43f0-9f74-2bc567cc02db",
+    "id" : "68ec036a-f399-4980-80b7-a27867f5e650",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2183,7 +2202,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "b42d375b-1cb9-4cc7-be7d-fae32791d0a8",
+    "id" : "aa32f681-942b-4194-b8df-124f210bcaa9",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2219,7 +2238,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "3c534feb-1736-4ff4-8187-192e49f21fd9",
+    "id" : "1169765a-7850-4a9a-9a72-9a9dcf75ac8b",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2248,7 +2267,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "5f3c06ca-8691-4c6c-8892-8d29563da9d0",
+    "id" : "60f23852-a761-4290-982b-c51876f4c382",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2263,7 +2282,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "76abf3cd-3635-494e-b75d-a3132a7933a5",
+    "id" : "a09aa7bd-3f8f-444d-83d0-f095b5f7c6bb",
     "alias" : "first broker login",
     "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
     "providerId" : "basic-flow",
@@ -2286,7 +2305,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d44f3e97-d11a-49ba-a3bf-7c8489de290d",
+    "id" : "85d2028a-ab84-4fcb-8088-94c927051018",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2308,7 +2327,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "7e60e213-6272-484c-b620-d7cf4f98f950",
+    "id" : "43ffa27d-3940-4b9b-857d-85f2b3729710",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2330,7 +2349,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "cce9e0e9-82f4-499d-a6f7-fc556248fc25",
+    "id" : "2e900379-7ae7-431d-a586-2014f7688aa0",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2346,7 +2365,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "61f08b26-e87b-4126-ac7a-c704d01e54dc",
+    "id" : "844942ca-41ae-4d50-b3ab-7815d9df6332",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2382,7 +2401,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d9ce077f-16c2-4c96-aba5-dd60bb3aa536",
+    "id" : "7c108a2a-abbd-462e-a9fc-917f28b67f80",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2418,7 +2437,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "8d4383f0-ebd2-4e0b-8d3f-2c6e1793c05b",
+    "id" : "b47ae4b9-a177-44ef-b41e-c7e5da6220c7",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2434,13 +2453,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "1206bd7a-0525-4e21-9bc7-d9ec62c1a4bc",
+    "id" : "81c9b7e4-2574-46b1-8a2a-e35edc716c1c",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "f5ff277d-b059-4dff-aaf8-8c5feeca1d4c",
+    "id" : "14491568-0d51-4082-9bb2-216d3cb4ab34",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java
index eff9f4ba22ed0430057084e57de173f00d954a85..aa4f25af3b379ac3731af006208dbe2299f2908b 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java
@@ -35,8 +35,8 @@ public class TableBriefDto {
     @Schema(example = "air_quality")
     private String internalName;
 
-    @NotNull(message = "creator is required")
-    private UserBriefDto creator;
+    @NotNull(message = "owner is required")
+    private UserBriefDto owner;
 
     @NotNull(message = "columns are required")
     @org.springframework.data.annotation.Transient
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableDto.java
index 22475b247b929aa6b884a46802d675bf708626a5..25315bd658cff125b4a0fba15564ec3c719d683b 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableDto.java
@@ -45,6 +45,9 @@ public class TableDto {
     @NotNull(message = "creator is required")
     private UserBriefDto creator;
 
+    @NotNull(message = "owner is required")
+    private UserBriefDto owner;
+
     @NotBlank(message = "queueName is required")
     @JsonProperty("queue_name")
     @Schema(example = "dbrepo/air_quality/air_quality")
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierCreateDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierCreateDto.java
index 67a9e858e95b8dc72b9074870de4e697346e72f8..e460cd8c59061e831aad1ba27c8378d5bd32e9f0 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierCreateDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierCreateDto.java
@@ -38,6 +38,10 @@ public class IdentifierCreateDto {
     @Schema(example = "Air quality reports at Stephansplatz, Vienna")
     private String description;
 
+    @NotNull
+    @Schema(example = "everyone")
+    private VisibilityTypeDto visibility;
+
     @JsonProperty("publication_day")
     @Schema(example = "15")
     private Integer publicationDay;
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
index 69e448003e42fcf4be8e0f40ead5b7a62ec7055d..1234beec9630665c55479a7c7929e3ffca2a86d9 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java
@@ -80,6 +80,10 @@ public class IdentifierDto {
     @Schema(example = "1")
     private Long resultNumber;
 
+    @NotNull
+    @Schema(example = "everyone")
+    private VisibilityTypeDto visibility;
+
     @Schema(example = "10.1038/nphys1170")
     private String doi;
 
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..19a913c61294907f4ffd65260cc616aebad8d260
--- /dev/null
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/identifier/VisibilityTypeDto.java
@@ -0,0 +1,26 @@
+package at.tuwien.api.identifier;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+public enum VisibilityTypeDto {
+
+    @JsonProperty("everyone")
+    EVERYONE("everyone"),
+
+    @JsonProperty("self")
+    SELF("self");
+
+    private String name;
+
+    VisibilityTypeDto(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+}
\ No newline at end of file
diff --git a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/user/UserUpdateDto.java b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/user/UserUpdateDto.java
index 108b54902e491615244797583670bc28b767d62f..a14c2e211fb3a1a6acd633f7894f1294f7f5ad49 100644
--- a/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/user/UserUpdateDto.java
+++ b/dbrepo-metadata-db/api/src/main/java/at/tuwien/api/user/UserUpdateDto.java
@@ -1,6 +1,5 @@
 package at.tuwien.api.user;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 
@@ -12,13 +11,6 @@ import lombok.*;
 @NoArgsConstructor
 public class UserUpdateDto {
 
-    @JsonProperty("titles_before")
-    @Schema(example = "Prof.")
-    private String titlesBefore;
-
-    @JsonProperty("titles_after")
-    private String titlesAfter;
-
     @Schema(example = "Josiah")
     private String firstname;
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
index e05a1e03ae060f17bfec5da1027d3141104fa096..75b506a6c6aff6ef94051ff6caee2e760b3dcef9 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/database/table/Table.java
@@ -45,6 +45,12 @@ public class Table {
     })
     private User creator;
 
+    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+    @JoinColumns({
+            @JoinColumn(name = "ownedBy", referencedColumnName = "ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false)
+    })
+    private User owner;
+
     @Column(nullable = false, name = "tname")
     private String name;
 
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
index 294a9c954db51f0144222de26f07e38e458be02e..6210ab7c35d4215f5d8090a7d9064dcd0b54f839 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/Identifier.java
@@ -101,6 +101,10 @@ public class Identifier implements Serializable {
     @Column
     private Integer publicationDay;
 
+    @Column(nullable = false, columnDefinition = "enum('EVERYONE', 'SELF')")
+    @Enumerated(EnumType.STRING)
+    private VisibilityType visibility;
+
     @OneToOne(fetch = FetchType.LAZY, cascade = {})
     @JoinColumns({
             @JoinColumn(name = "dbid", referencedColumnName = "id", insertable = false, updatable = false)
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java
new file mode 100644
index 0000000000000000000000000000000000000000..3565740c6b2042fd4fdb003e68a19f369710efcf
--- /dev/null
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/identifier/VisibilityType.java
@@ -0,0 +1,11 @@
+package at.tuwien.entities.identifier;
+
+import lombok.Getter;
+import lombok.ToString;
+
+@Getter
+@ToString
+public enum VisibilityType {
+    EVERYONE,
+    SELF;
+}
\ No newline at end of file
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java
index a06563f1d6c0d6398ba5fba9665cc846d8a7aeeb..17a0446bc87509b8ddb4754815b8e4f18fb06e57 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/Role.java
@@ -1,12 +1,12 @@
 package at.tuwien.entities.user;
 
 import lombok.*;
-import org.hibernate.annotations.GenericGenerator;
 import org.hibernate.annotations.Immutable;
 import org.hibernate.annotations.Type;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
 import javax.persistence.*;
+import java.util.List;
 import java.util.UUID;
 
 @Data
@@ -30,18 +30,12 @@ public class Role {
     @Column(name = "NAME", nullable = false)
     private String name;
 
-    @Column(name = "REALM_ID", nullable = false)
-    private String realmId;
+    @Column(name = "REALM_ID", nullable = false, columnDefinition = "VARCHAR(36)")
+    @Type(type = "uuid-char")
+    private UUID realmId;
 
     @ToString.Exclude
-    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
-    @JoinTable(name = "user_role_mapping",
-            joinColumns = {
-                    @JoinColumn(name = "ROLE_ID", referencedColumnName = "ID", insertable = false, updatable = false),
-            },
-            inverseJoinColumns = {
-                    @JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false),
-            })
-    private User user;
+    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles")
+    private List<User> users;
 
 }
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java
index 19b46e3307ab3b03768e2380bb85870f91cdf4b4..08d5df4b4d1ae70f6dbe15dd86cd45352141003c 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/RoleMapping.java
@@ -16,7 +16,9 @@ import java.util.UUID;
 @IdClass(RoleMappingKey.class)
 @EntityListeners(AuditingEntityListener.class)
 @EqualsAndHashCode(onlyExplicitlyIncluded = true)
-@Table(name = "user_role_mapping")
+@Table(name = "user_role_mapping", uniqueConstraints = {
+        @UniqueConstraint(columnNames = {"USER_ID", "ROLE_ID"})
+})
 public class RoleMapping {
 
     @Id
diff --git a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
index 98e6fbda337b38df9f931a586e9e5034d41fe58a..109e63b06dc8f96e0ddfce6328901f5c34fc87ca 100644
--- a/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
+++ b/dbrepo-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
@@ -69,11 +69,6 @@ public class User {
     @Column(nullable = false)
     private String databasePassword;
 
-    @Column(nullable = false)
-    @ToString.Exclude
-    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
-    private List<Role> roles;
-
     @Column(nullable = false)
     @ToString.Exclude
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
@@ -99,6 +94,17 @@ public class User {
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "creator")
     private List<Identifier> identifiers;
 
+    @ToString.Exclude
+    @ManyToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "user_role_mapping",
+            joinColumns = {
+                    @JoinColumn(name = "USER_ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false),
+            },
+            inverseJoinColumns = {
+                    @JoinColumn(name = "ROLE_ID", nullable = false, columnDefinition = "VARCHAR(36)", updatable = false),
+            })
+    private List<Role> roles;
+
     @Transient
     @ToString.Exclude
     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "creator")
diff --git a/dbrepo-metadata-db/setup-schema.sql b/dbrepo-metadata-db/setup-schema.sql
index 31eefbbbd3700c3be6724c81caac9b7c4cb643a1..9529463f2471d5163e4be25ad923caaf56da1ba1 100644
--- a/dbrepo-metadata-db/setup-schema.sql
+++ b/dbrepo-metadata-db/setup-schema.sql
@@ -126,6 +126,7 @@ CREATE TABLE IF NOT EXISTS `fda`.`mdb_tables`
     Version       TEXT,
     created       timestamp              NOT NULL DEFAULT NOW(),
     created_by    character varying(255) NOT NULL,
+    owned_by      character varying(255) NOT NULL,
     last_modified timestamp,
     PRIMARY KEY (ID, tDBID),
     FOREIGN KEY (tDBID) REFERENCES mdb_databases (id)
@@ -348,19 +349,20 @@ CREATE TABLE IF NOT EXISTS `fda`.`mdb_view_columns`
 
 CREATE TABLE IF NOT EXISTS `fda`.`mdb_identifiers`
 (
-    id                bigint                               NOT NULL AUTO_INCREMENT,
-    cid               bigint                               NOT NULL,
-    dbid              bigint                               NOT NULL,
+    id                bigint                    NOT NULL AUTO_INCREMENT,
+    cid               bigint                    NOT NULL,
+    dbid              bigint                    NOT NULL,
     qid               bigint,
-    title             VARCHAR(255)                         NOT NULL,
-    publisher         VARCHAR(255)                         NOT NULL,
+    title             VARCHAR(255)              NOT NULL,
+    publisher         VARCHAR(255)              NOT NULL,
     language          VARCHAR(50),
     license           VARCHAR(50),
     description       TEXT,
-    publication_year  INTEGER                              NOT NULL,
+    visibility        ENUM ('SELF', 'EVERYONE') NOT NULL default 'EVERYONE',
+    publication_year  INTEGER                   NOT NULL,
     publication_month INTEGER,
     publication_day   INTEGER,
-    identifier_type   varchar(50)                          NOT NULL,
+    identifier_type   varchar(50)               NOT NULL,
     query             TEXT,
     query_normalized  TEXT,
     query_hash        VARCHAR(255),
@@ -368,8 +370,8 @@ CREATE TABLE IF NOT EXISTS `fda`.`mdb_identifiers`
     result_hash       VARCHAR(255),
     result_number     bigint,
     doi               VARCHAR(255),
-    created           timestamp                            NOT NULL DEFAULT NOW(),
-    created_by        character varying(255)               NOT NULL,
+    created           timestamp                 NOT NULL DEFAULT NOW(),
+    created_by        character varying(255)    NOT NULL,
     last_modified     timestamp,
     PRIMARY KEY (id), /* must be a single id from persistent identifier concept */
     FOREIGN KEY (cid) REFERENCES mdb_containers (id),
diff --git a/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
index 43b2d75a3aebe49d85f0d5197539597bee790cc0..987f301117ffd757bf26f187e11e3d561e6818fb 100644
--- a/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
+++ b/dbrepo-metadata-db/test/src/main/java/at/tuwien/test/BaseTest.java
@@ -31,6 +31,7 @@ import at.tuwien.entities.database.table.constraints.foreignKey.ForeignKeyRefere
 import at.tuwien.entities.database.table.constraints.unique.Unique;
 import at.tuwien.entities.identifier.*;
 import at.tuwien.entities.user.Realm;
+import at.tuwien.entities.user.Role;
 import at.tuwien.entities.user.User;
 import at.tuwien.entities.user.UserAttribute;
 import at.tuwien.querystore.Query;
@@ -134,14 +135,17 @@ public abstract class BaseTest {
     public final static String[] DEFAULT_USER_HANDLING = new String[]{"default-user-handling", "modify-user-theme",
             "modify-user-information"};
 
+    public final static String[] ESCALATED_USER_HANDLING = new String[]{"escalated-user-handling", "find-user"};
+
     public final static String[] DEFAULT_RESEARCHER_ROLES = ArrayUtil.merge(List.of(new String[]{"default-researcher-roles"},
             DEFAULT_CONTAINER_HANDLING, DEFAULT_DATABASE_HANDLING, DEFAULT_IDENTIFIER_HANDLING, DEFAULT_QUERY_HANDLING,
             DEFAULT_TABLE_HANDLING, DEFAULT_USER_HANDLING));
 
     public final static String[] DEFAULT_DEVELOPER_ROLES = ArrayUtil.merge(List.of(new String[]{"default-developer-roles"},
             DEFAULT_CONTAINER_HANDLING, DEFAULT_DATABASE_HANDLING, DEFAULT_IDENTIFIER_HANDLING, DEFAULT_QUERY_HANDLING,
-            DEFAULT_TABLE_HANDLING, DEFAULT_USER_HANDLING, ESCALATED_CONTAINER_HANDLING, ESCALATED_DATABASE_HANDLING,
-            ESCALATED_IDENTIFIER_HANDLING, ESCALATED_QUERY_HANDLING, ESCALATED_TABLE_HANDLING));
+            DEFAULT_TABLE_HANDLING, DEFAULT_USER_HANDLING, ESCALATED_USER_HANDLING, ESCALATED_CONTAINER_HANDLING,
+            ESCALATED_DATABASE_HANDLING, ESCALATED_IDENTIFIER_HANDLING, ESCALATED_QUERY_HANDLING,
+            ESCALATED_TABLE_HANDLING));
 
     public final static List<GrantedAuthorityDto> AUTHORITY_DEFAULT_RESEARCHER_ROLES = Arrays.stream(DEFAULT_RESEARCHER_ROLES)
             .map(GrantedAuthorityDto::new)
@@ -177,6 +181,16 @@ public abstract class BaseTest {
             .enabled(REALM_DBREPO_ENABLED)
             .build();
 
+    public final static UUID ROLE_DEFAULT_RESEARCHER_ROLES_ID = UUID.fromString("c74cbbe7-3ab1-4472-9211-cc9045672682");
+    public final static String ROLE_DEFAULT_RESEARCHER_ROLES_NAME = "default-researcher-roles";
+    public final static UUID ROLE_DEFAULT_RESEARCHER_ROLES_REALM_ID = REALM_DBREPO_ID;
+
+    public final static Role ROLE_DEFAULT_RESEARCHER_ROLES = Role.builder()
+            .id(ROLE_DEFAULT_RESEARCHER_ROLES_ID)
+            .name(ROLE_DEFAULT_RESEARCHER_ROLES_NAME)
+            .realmId(ROLE_DEFAULT_RESEARCHER_ROLES_REALM_ID)
+            .build();
+
     public final static String USER_BROKER_USERNAME = "guest";
     public final static String USER_BROKER_PASSWORD = "guest";
 
diff --git a/dbrepo-proxy/dbrepo.conf b/dbrepo-proxy/dbrepo.conf
index 66470c4e92ab97c3d4ae4fac78d4000d26f9e308..ac955002347f24a0c292ecc1eb4efe7f363db680 100644
--- a/dbrepo-proxy/dbrepo.conf
+++ b/dbrepo-proxy/dbrepo.conf
@@ -1,4 +1,4 @@
-client_max_body_size 100G;
+client_max_body_size 2G;
 
 server {
     listen 80 default_server;
diff --git a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
index d80b31df72e06474407e86455fe757574e99162e..fb3b7209d06c8d797293521b75cb630d095cac32 100644
--- a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
+++ b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
@@ -18,7 +18,6 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.Authentication;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -34,12 +33,14 @@ public class QueryEndpoint {
 
     private final QueryService queryService;
     private final StoreService storeService;
+    private final AccessService accessService;
     private final DatabaseService databaseService;
     private final EndpointValidator endpointValidator;
 
     @Autowired
-    public QueryEndpoint(QueryService queryService, StoreService storeService,
+    public QueryEndpoint(QueryService queryService, StoreService storeService, AccessService accessService,
                          DatabaseService databaseService, EndpointValidator endpointValidator) {
+        this.accessService = accessService;
         this.databaseService = databaseService;
         this.endpointValidator = endpointValidator;
         this.queryService = queryService;
@@ -71,18 +72,8 @@ public class QueryEndpoint {
         }
         endpointValidator.validateForbiddenStatements(data);
         endpointValidator.validateDataParams(page, size, sortDirection, sortColumn);
-        final Database database = databaseService.find(containerId, databaseId);
-        if (!database.getIsPublic()) {
-            if (principal == null) {
-                log.error("Failed to execute private query: principal is null");
-                throw new NotAllowedException("Failed to re-execute private query: principal is null");
-            }
-            final Authentication authentication = (Authentication) principal;
-            if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("execute-query"))) {
-                log.error("Failed to execute private query: role missing");
-                throw new NotAllowedException("Failed to re-execute private query: role missing");
-            }
-        }
+        /* has access */
+        accessService.find(databaseId, principal.getName());
         /* execute */
         final QueryResultDto result = queryService.execute(containerId, databaseId, data, principal, page, size,
                 sortDirection, sortColumn);
diff --git a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
index 7c0da7215e72955570bf276ce5352070c845f2d5..29830f33a7c7908c8acb9313b7eee4c6b3dcbfb9 100644
--- a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
+++ b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
@@ -43,18 +43,20 @@ public class StoreEndpoint {
     private final QueryMapper queryMapper;
     private final UserService userService;
     private final StoreService storeService;
+    private final AccessService accessService;
     private final IdentifierMapper identifierMapper;
     private final EndpointValidator endpointValidator;
     private final IdentifierService identifierService;
 
     @Autowired
     public StoreEndpoint(UserMapper userMapper, QueryMapper queryMapper, UserService userService,
-                         StoreService storeService, IdentifierMapper identifierMapper,
+                         StoreService storeService, AccessService accessService, IdentifierMapper identifierMapper,
                          EndpointValidator endpointValidator, IdentifierService identifierService) {
         this.userMapper = userMapper;
         this.queryMapper = queryMapper;
         this.userService = userService;
         this.storeService = storeService;
+        this.accessService = accessService;
         this.identifierMapper = identifierMapper;
         this.endpointValidator = endpointValidator;
         this.identifierService = identifierService;
@@ -109,7 +111,7 @@ public class StoreEndpoint {
             DatabaseConnectionException, TableMalformedException, UserNotFoundException, NotAllowedException {
         log.debug("endpoint list queries, containerId={}, databaseId={}, persisted={}, principal={}", containerId,
                 databaseId, persisted, principal);
-        endpointValidator.validateOnlyAccess(containerId, databaseId, principal);
+        endpointValidator.validateOnlyAccessOrPublic(containerId, databaseId, principal);
         final List<Query> queries = storeService.findAll(containerId, databaseId, persisted, principal);
         final List<Identifier> identifiers = identifierService.findAll();
         final List<User> users = userService.findAll();
@@ -176,7 +178,9 @@ public class StoreEndpoint {
             DatabaseConnectionException {
         log.debug("endpoint find query, containerId={}, databaseId={}, queryId={}, principal={}", containerId, databaseId,
                 queryId, principal);
-        endpointValidator.validateOnlyAccess(containerId, databaseId, principal);
+        /* check */
+        endpointValidator.validateOnlyAccessOrPublic(containerId, databaseId, principal);
+        /* find */
         final Query query = storeService.findOne(containerId, databaseId, queryId, principal);
         final QueryDto dto = queryMapper.queryToQueryDto(query);
         final User creator = userService.findByUsername(query.getCreatedBy());
@@ -237,7 +241,8 @@ public class StoreEndpoint {
             QueryAlreadyPersistedException, NotAllowedException {
         log.debug("endpoint persist query, container, containerId={}, databaseId={}, queryId={}, principal={}",
                 containerId, databaseId, queryId, principal);
-        endpointValidator.validateOnlyAccess(containerId, databaseId, principal);
+        /* check */
+        endpointValidator.validateOnlyAccessOrPublic(containerId, databaseId, principal);
         final Query check = storeService.findOne(containerId, databaseId, queryId, principal);
         if (!check.getCreatedBy().equals(principal.getName())) {
             log.error("Cannot persist foreign query: created by {}", check.getCreatedBy());
@@ -247,6 +252,9 @@ public class StoreEndpoint {
             log.error("Failed to persist, is already persisted");
             throw new QueryAlreadyPersistedException("Failed to persist");
         }
+        /* has access */
+        accessService.find(databaseId, principal.getName());
+        /* persist */
         final Query query = storeService.persist(containerId, databaseId, queryId, principal);
         final QueryDto dto = queryMapper.queryToQueryDto(query);
         final User creator = userService.findByUsername(query.getCreatedBy());
diff --git a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
index b738a891b56004663ce8127f1ec8ebe3c9d5375b..f53f5d489728afd6018e3143c1b728d78b59ffc5 100644
--- a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
+++ b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
@@ -6,6 +6,8 @@ import at.tuwien.api.database.query.QueryResultDto;
 import at.tuwien.api.database.table.TableCsvDeleteDto;
 import at.tuwien.api.database.table.TableCsvDto;
 import at.tuwien.api.database.table.TableCsvUpdateDto;
+import at.tuwien.entities.database.Database;
+import at.tuwien.entities.user.User;
 import at.tuwien.exception.*;
 import at.tuwien.service.*;
 import at.tuwien.validation.EndpointValidator;
@@ -31,11 +33,14 @@ import java.time.Instant;
 public class TableDataEndpoint {
 
     private final QueryService queryService;
+    private final DatabaseService databaseService;
     private final EndpointValidator endpointValidator;
 
     @Autowired
-    public TableDataEndpoint(QueryService queryService, EndpointValidator endpointValidator) {
+    public TableDataEndpoint(QueryService queryService, DatabaseService databaseService,
+                             EndpointValidator endpointValidator) {
         this.queryService = queryService;
+        this.databaseService = databaseService;
         this.endpointValidator = endpointValidator;
     }
 
@@ -51,9 +56,12 @@ public class TableDataEndpoint {
                                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
             ImageNotSupportedException, ContainerNotFoundException, DatabaseConnectionException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
         log.debug("endpoint insert data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
+        /* check */
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(containerId, databaseId, tableId, principal);
+        /* insert */
         queryService.insert(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -72,9 +80,12 @@ public class TableDataEndpoint {
                                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
             ImageNotSupportedException, DatabaseConnectionException, QueryMalformedException,
-            UserNotFoundException {
+            UserNotFoundException, NotAllowedException {
         log.debug("endpoint update data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
+        /* check */
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(containerId, databaseId, tableId, principal);
+        /* update */
         queryService.update(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -92,9 +103,12 @@ public class TableDataEndpoint {
                                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
             ImageNotSupportedException, ContainerNotFoundException,
-            DatabaseConnectionException, QueryMalformedException, UserNotFoundException {
+            DatabaseConnectionException, QueryMalformedException, UserNotFoundException, NotAllowedException {
         log.debug("endpoint delete data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
+        /* check */
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(containerId, databaseId, tableId, principal);
+        /* delete */
         queryService.delete(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -112,9 +126,12 @@ public class TableDataEndpoint {
                                           @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
             ImageNotSupportedException, ContainerNotFoundException, DatabaseConnectionException,
-            QueryMalformedException, UserNotFoundException {
+            QueryMalformedException, UserNotFoundException, NotAllowedException {
         log.debug("endpoint insert data from csv, containerId={}, databaseId={}, tableId={}, data={}, principal={}",
                 containerId, databaseId, tableId, data, principal);
+        /* check */
+        endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(containerId, databaseId, tableId, principal);
+        /* insert */
         queryService.insert(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -135,11 +152,18 @@ public class TableDataEndpoint {
                                                  @RequestParam(required = false) String sortColumn)
             throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException,
             ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException,
-            QueryMalformedException, UserNotFoundException, SortException {
+            QueryMalformedException, UserNotFoundException, SortException, NotAllowedException {
         log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}, page={}, size={}, sortDirection={}, sortColumn={}",
                 containerId, databaseId, tableId, principal, timestamp, page, size, sortDirection, sortColumn);
         /* check */
         endpointValidator.validateDataParams(page, size, sortDirection, sortColumn);
+        endpointValidator.validateOnlyAccessOrPublic(containerId, databaseId, principal);
+        final Database database = databaseService.find(containerId, databaseId);
+        if (!database.getIsPublic() && !User.hasRole(principal, "view-table-data")) {
+            log.error("Failed to view table data: database with id {} is private and user has no authority", databaseId);
+            throw new NotAllowedException("Failed to view table data: database with id " + databaseId + " is private and user has no authority");
+        }
+        /* find */
         final QueryResultDto response = queryService.tableFindAll(containerId, databaseId, tableId, timestamp, page, size, principal);
         log.trace("find table data resulted in result {}", response);
         return ResponseEntity.ok()
@@ -147,18 +171,26 @@ public class TableDataEndpoint {
     }
 
     @GetMapping("/count")
+    @Transactional(readOnly = true)
     @Timed(value = "data.all.count", description = "Time needed to get count of all data from a table")
     @Operation(summary = "Find data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Long> getCount(@NotNull @PathVariable("id") Long containerId,
-                                                 @NotNull @PathVariable("databaseId") Long databaseId,
-                                                 @NotNull @PathVariable("tableId") Long tableId,
-                                                 @NotNull Principal principal,
-                                                 @RequestParam(required = false) Instant timestamp)
+                                         @NotNull @PathVariable("databaseId") Long databaseId,
+                                         @NotNull @PathVariable("tableId") Long tableId,
+                                         @NotNull Principal principal,
+                                         @RequestParam(required = false) Instant timestamp)
             throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException,
             ImageNotSupportedException, TableMalformedException, ContainerNotFoundException,
-            QueryStoreException, QueryMalformedException, UserNotFoundException {
+            QueryStoreException, QueryMalformedException, UserNotFoundException, NotAllowedException {
         log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}",
                 containerId, databaseId, tableId, principal, timestamp);
+        /* check */
+        endpointValidator.validateOnlyAccessOrPublic(containerId, databaseId, principal);
+        final Database database = databaseService.find(containerId, databaseId);
+        if (!database.getIsPublic() && !User.hasRole(principal, "view-table-data")) {
+            log.error("Failed to view table data: database with id {} is private and user has no authority", databaseId);
+            throw new NotAllowedException("Failed to view table data: database with id " + databaseId + " is private and user has no authority");
+        }
         /* find */
         final Long count = queryService.tableCount(containerId, databaseId, tableId, timestamp, principal);
         log.debug("table data count is {} tuples", count);
diff --git a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
index c264c9f50c24c96b31b98120155d95ed8e8a7605..435752f3088041e4d45f9d5a64abca0c0fe165c2 100644
--- a/dbrepo-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
+++ b/dbrepo-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
@@ -3,13 +3,14 @@ package at.tuwien.validation;
 import at.tuwien.SortType;
 import at.tuwien.api.database.query.ExecuteStatementDto;
 import at.tuwien.config.QueryConfig;
+import at.tuwien.entities.database.AccessType;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.DatabaseAccess;
-import at.tuwien.entities.user.User;
+import at.tuwien.entities.database.table.Table;
 import at.tuwien.exception.*;
 import at.tuwien.service.AccessService;
 import at.tuwien.service.DatabaseService;
-import at.tuwien.service.UserService;
+import at.tuwien.service.TableService;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -25,16 +26,16 @@ import java.util.regex.Pattern;
 @Component
 public class EndpointValidator {
 
-    private final UserService userService;
     private final QueryConfig queryConfig;
+    private final TableService tableService;
     private final AccessService accessService;
     private final DatabaseService databaseService;
 
     @Autowired
-    public EndpointValidator(UserService userService, QueryConfig queryConfig, AccessService accessService,
-                             DatabaseService databaseService) {
-        this.userService = userService;
+    public EndpointValidator(QueryConfig queryConfig, TableService tableService,
+                             AccessService accessService, DatabaseService databaseService) {
         this.queryConfig = queryConfig;
+        this.tableService = tableService;
         this.accessService = accessService;
         this.databaseService = databaseService;
     }
@@ -88,7 +89,8 @@ public class EndpointValidator {
         throw new QueryMalformedException("Query contains forbidden keyword(s): " + Arrays.toString(words.toArray()));
     }
 
-    public void validateOnlyAccess(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException, NotAllowedException {
+    public void validateOnlyAccessOrPublic(Long containerId, Long databaseId, Principal principal)
+            throws DatabaseNotFoundException, NotAllowedException {
         final Database database = databaseService.find(containerId, databaseId);
         if (database.getIsPublic()) {
             log.trace("database with id {} is public: no access needed", databaseId);
@@ -104,4 +106,27 @@ public class EndpointValidator {
         log.trace("found access {}", access);
     }
 
+    public void validateOnlyWriteOwnOrWriteAllAccess(Long containerId, Long databaseId, Long tableId,
+                                                     Principal principal)
+            throws DatabaseNotFoundException, TableNotFoundException, NotAllowedException {
+        final Table table = tableService.find(containerId, databaseId, tableId);
+        if (principal == null) {
+            log.error("Access not allowed: no authorization provided");
+            throw new NotAllowedException("Access not allowed: no authorization provided");
+        }
+        log.trace("principal is {}", principal);
+        final DatabaseAccess access = accessService.find(databaseId, principal.getName());
+        log.trace("found access {}", access);
+        if (access.getType().equals(AccessType.WRITE_ALL)) {
+            log.debug("user {} has write-all access, skip.", principal.getName());
+            return;
+        }
+        if (table.getOwner().getUsername().equals(principal.getName()) && access.getType().equals(AccessType.WRITE_OWN)) {
+            log.debug("user {} has write-own access to their own table, skip.", principal.getName());
+            return;
+        }
+        log.error("Access not allowed: no write access for table with id {}", tableId);
+        throw new NotAllowedException("Access not allowed: no write access for table with id " + tableId);
+    }
+
 }
diff --git a/dbrepo-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java b/dbrepo-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java
index c15a10770ba3dbcc5956e675cd9fb14f82f08cbc..c6820d5653dac61acc871c0734b97d01632bc981 100644
--- a/dbrepo-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java
+++ b/dbrepo-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java
@@ -287,7 +287,7 @@ public class TableDataEndpointUnitTest extends BaseUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void getAll_publicAnonymousSizeNull_fails()  {
+    public void getAll_publicAnonymousSizeNull_fails() {
 
         /* test */
         assertThrows(PaginationException.class, () -> {
@@ -309,7 +309,7 @@ public class TableDataEndpointUnitTest extends BaseUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void getAll_publicAnonymousSizeNegative_fails()  {
+    public void getAll_publicAnonymousSizeNegative_fails() {
 
         /* test */
         assertThrows(PaginationException.class, () -> {
@@ -331,13 +331,32 @@ public class TableDataEndpointUnitTest extends BaseUnitTest {
 
     @Test
     @WithAnonymousUser
-    public void getAll_privateAnonymous_fails() throws UserNotFoundException, TableNotFoundException,
-            QueryStoreException, SortException, TableMalformedException, NotAllowedException,
-            DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException,
-            PaginationException, ContainerNotFoundException {
+    public void getAll_privateAnonymous_fails() {
 
         /* test */
-        generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, null, null, null, null, null, null, null);
+        assertThrows(NotAllowedException.class, () -> {
+            generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, null, null, null, null, null, null, null);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_3_USERNAME, authorities = {})
+    public void getAll_privateNoRole_fails() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_3_USERNAME, DATABASE_1_RESEARCHER_READ_ACCESS, USER_3_PRINCIPAL, null, null, null, null, null);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_3_USERNAME, authorities = {})
+    public void getCount_privateNoRole_fails() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            generic_getCount(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_3_USERNAME, DATABASE_1_RESEARCHER_READ_ACCESS, USER_3_PRINCIPAL, null);
+        });
     }
 
     public static Stream<Arguments> getAll_succeeds_parameters() {
diff --git a/dbrepo-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/dbrepo-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
index 1421c00dba97adf6817b32d46337eda1b39a881d..536aff8f662e9e135f699b167475d7190ccdd932 100644
--- a/dbrepo-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
+++ b/dbrepo-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
@@ -437,7 +437,6 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
                     .executeUpdate();
         } catch (SQLException | IOException e) {
             log.error("Failed to insert temporary table: {}", e.getMessage());
-            log.trace("failed to insert temporary table {}", table);
             dataSource.close();
             throw new TableMalformedException("Failed to insert temporary table", e);
         } finally {
diff --git a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableColumnEndpointUnitTest.java b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableColumnEndpointUnitTest.java
index d1dd3b32603f7e4ebf80c9eb20b5bf30ad61071c..3b9d220e7eff3f0362c860421f4b9f31c5686cb0 100644
--- a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableColumnEndpointUnitTest.java
+++ b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableColumnEndpointUnitTest.java
@@ -1,16 +1,11 @@
 package at.tuwien.endpoint;
 
 import at.tuwien.BaseUnitTest;
-import at.tuwien.api.database.table.TableBriefDto;
-import at.tuwien.api.database.table.TableCreateDto;
-import at.tuwien.api.database.table.TableDto;
 import at.tuwien.api.database.table.columns.ColumnDto;
 import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto;
 import at.tuwien.config.IndexConfig;
 import at.tuwien.config.ReadyConfig;
 import at.tuwien.endpoints.TableColumnEndpoint;
-import at.tuwien.endpoints.TableEndpoint;
-import at.tuwien.entities.container.Container;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.DatabaseAccess;
 import at.tuwien.entities.database.table.Table;
@@ -19,12 +14,10 @@ import at.tuwien.exception.*;
 import at.tuwien.repository.elastic.TableColumnIdxRepository;
 import at.tuwien.repository.elastic.TableIdxRepository;
 import at.tuwien.service.AccessService;
-import at.tuwien.service.ContainerService;
 import at.tuwien.service.DatabaseService;
 import at.tuwien.service.TableService;
 import com.rabbitmq.client.Channel;
 import lombok.extern.log4j.Log4j2;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -32,7 +25,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.test.context.support.WithAnonymousUser;
@@ -40,7 +32,6 @@ import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
 import java.security.Principal;
-import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
diff --git a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointIntegrationTest.java b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointIntegrationTest.java
index 40f8b173051ab18a42ce9b516adb4799cd4fe9aa..2d473bd693228c5bf1b17f308523bc11dc1a0964 100644
--- a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointIntegrationTest.java
+++ b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointIntegrationTest.java
@@ -5,15 +5,11 @@ import at.tuwien.config.DockerConfig;
 import at.tuwien.config.H2Utils;
 import at.tuwien.config.IndexConfig;
 import at.tuwien.config.ReadyConfig;
-import at.tuwien.endpoints.TableColumnEndpoint;
 import at.tuwien.endpoints.TableEndpoint;
 import at.tuwien.exception.*;
 import at.tuwien.repository.elastic.TableColumnIdxRepository;
 import at.tuwien.repository.elastic.TableIdxRepository;
 import at.tuwien.repository.jpa.*;
-import at.tuwien.service.AccessService;
-import at.tuwien.service.DatabaseService;
-import at.tuwien.service.TableService;
 import com.rabbitmq.client.Channel;
 import lombok.extern.log4j.Log4j2;
 import org.junit.jupiter.api.AfterEach;
@@ -28,9 +24,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.doThrow;
-
 @Log4j2
 @EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
 @SpringBootTest
diff --git a/dbrepo-table-service/rest-service/src/test/java/at/tuwien/mapper/TableMapperUnitTest.java b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/mapper/TableMapperUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..234ce6491ee4cd2468bd73ee7197f5e467d26b76
--- /dev/null
+++ b/dbrepo-table-service/rest-service/src/test/java/at/tuwien/mapper/TableMapperUnitTest.java
@@ -0,0 +1,71 @@
+package at.tuwien.mapper;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.api.database.AccessTypeDto;
+import at.tuwien.config.IndexConfig;
+import at.tuwien.config.ReadyConfig;
+import at.tuwien.exception.NotAllowedException;
+import at.tuwien.repository.elastic.TableColumnIdxRepository;
+import at.tuwien.repository.elastic.TableIdxRepository;
+import com.rabbitmq.client.Channel;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class TableMapperUnitTest extends BaseUnitTest {
+
+    @MockBean
+    private ReadyConfig readyConfig;
+
+    @MockBean
+    private Channel channel;
+
+    @MockBean
+    private IndexConfig indexInitializer;
+
+    @MockBean
+    private TableIdxRepository tableidxRepository;
+
+    @MockBean
+    private TableColumnIdxRepository tableColumnidxRepository;
+
+    @Autowired
+    private TableMapper tableMapper;
+
+    public static Stream<Arguments> nameToInternalName_parameters() {
+        return Stream.of(
+                Arguments.arguments("dash_minus", "OE/NO-027", "oe_no_027"),
+                Arguments.arguments("percent", "OE%NO-027", "oe_no_027"),
+                Arguments.arguments("umlaut", "OE/NĂ–-027", "oe_no__027"),
+                Arguments.arguments("dot", "OE.NO-027", "oe_no_027")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("nameToInternalName_parameters")
+    public void nameToInternalName_succeeds(String name, String request, String compare) {
+
+        /* test */
+        final String response = tableMapper.nameToInternalName(request);
+        assertEquals(compare, response);
+    }
+
+}
diff --git a/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java b/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
index d12f4c9e432f0cc081abca833905d6fcda59e266..c6f7a253ec7ebbea23ccbfc917fab48d90d9e81a 100644
--- a/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
+++ b/dbrepo-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java
@@ -226,7 +226,8 @@ public interface TableMapper {
         final Pattern WHITESPACE = Pattern.compile("[\\s]");
         String nowhitespace = WHITESPACE.matcher(data).replaceAll("_");
         String normalized = Normalizer.normalize(nowhitespace, Normalizer.Form.NFD);
-        String slug = NONLATIN.matcher(normalized).replaceAll("");
+        String slug = NONLATIN.matcher(normalized).replaceAll("_")
+                .replaceAll("-", "_");
         final String name = slug.toLowerCase(Locale.ENGLISH);
         log.trace("mapped name {} to internal name {}", data, name);
         return name;
diff --git a/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
index 8523187a3ae8c0e28689f7d9b9cd13ea985c0732..d60ac66548bb76d27fffc6875d44c5291fdc6204 100644
--- a/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
+++ b/dbrepo-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java
@@ -170,6 +170,7 @@ public class TableServiceImpl extends HibernateConnector implements TableService
         tmp.setConstraints(null);
         final User creator = userService.findByUsername(principal.getName());
         tmp.setCreator(creator);
+        tmp.setOwner(creator);
         /* save in metadata database */
         final Table entity = tableRepository.save(tmp);
         entity.setColumns(createDto.getColumns()
diff --git a/dbrepo-ui/api/database.service.js b/dbrepo-ui/api/database.service.js
index 85756eaca0db53e7b963558abcb68af4a8a51de4..c01fc2ceae0933c0df03b49cdeccfc89d786b013 100644
--- a/dbrepo-ui/api/database.service.js
+++ b/dbrepo-ui/api/database.service.js
@@ -148,7 +148,7 @@ class DatabaseService {
 
   giveAccess (id, databaseId, username, type) {
     return new Promise((resolve, reject) => {
-      api.post(`/api/container/${id}/database/${databaseId}/access/${username}`, { username, type }, { headers: { Accept: 'application/json' } })
+      api.post(`/api/container/${id}/database/${databaseId}/access`, { username, type }, { headers: { Accept: 'application/json' } })
         .then(() => resolve())
         .catch((error) => {
           const { code, message } = error
diff --git a/dbrepo-ui/api/database.utils.js b/dbrepo-ui/api/database.utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..38adfddd8f54fa59b524ed65e956d3d662068b48
--- /dev/null
+++ b/dbrepo-ui/api/database.utils.js
@@ -0,0 +1,10 @@
+class DatabaseUtils {
+  isOwner (database, user) {
+    if (!database || !user) {
+      return false
+    }
+    return database.owner.id === user.id
+  }
+}
+
+export default new DatabaseUtils()
diff --git a/dbrepo-ui/api/query.service.js b/dbrepo-ui/api/query.service.js
index a7900546e0cd3cf7a7c99db12c7a677296673bc3..db1bc16f3a4c9fd21b125a56182a0e1cc048595a 100644
--- a/dbrepo-ui/api/query.service.js
+++ b/dbrepo-ui/api/query.service.js
@@ -151,7 +151,7 @@ class QueryService {
 
   execute (id, databaseId, data, page, size) {
     return new Promise((resolve, reject) => {
-      api.post(`/api/container/${id}/database/${databaseId}?page=${page}&size=${size}`, data, { headers: { Accept: 'application/json' } })
+      api.post(`/api/container/${id}/database/${databaseId}/query?page=${page}&size=${size}`, data, { headers: { Accept: 'application/json' } })
         .then((response) => {
           const result = response.data
           console.debug('response result', result)
diff --git a/dbrepo-ui/api/table.service.js b/dbrepo-ui/api/table.service.js
index 930aae5a8ee79ef8f008860fbae3cd1f7de94cc0..8cef6aff3f3317efbae7093aeda5a5fea6cc5f0b 100644
--- a/dbrepo-ui/api/table.service.js
+++ b/dbrepo-ui/api/table.service.js
@@ -58,6 +58,19 @@ class TableService {
     })
   }
 
+  importCsv (id, databaseId, tableId, data) {
+    return new Promise((resolve, reject) => {
+      api.post(`/api/container/${id}/database/${databaseId}/table/${tableId}/data/import`, data, { headers: { Accept: 'application/json' } })
+        .then(() => resolve())
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to import table data', error)
+          Vue.$toast.error(`[${code}] Failed to import table data: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
   data (id, databaseId, tableId, page, size, timestamp) {
     return new Promise((resolve, reject) => {
       api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/data?page=${page}&size=${size}&timestamp=${timestamp}`, { headers: { Accept: 'application/json' } })
diff --git a/dbrepo-ui/api/user.service.js b/dbrepo-ui/api/user.service.js
index e324cf68f3b351bca19cade29fe1aedd388d5f49..0fe5a464bd50a40b9af0a945dc86ec52eae2ecb7 100644
--- a/dbrepo-ui/api/user.service.js
+++ b/dbrepo-ui/api/user.service.js
@@ -63,9 +63,9 @@ class UserService {
           const { code, message, response } = error
           const { status } = response
           if (status === 417) {
-            Vue.$toast.error(`[${code}] This e-mail address is taken: ${message}`)
+            Vue.$toast.error('This e-mail address is already taken')
           } else if (status === 409) {
-            Vue.$toast.error(`[${code}] This username is taken: ${message}`)
+            Vue.$toast.error('This username is already taken')
           } else if (status === 428) {
             Vue.$toast.warning(`[${code}] Account was created: ${message}`)
           } else {
diff --git a/dbrepo-ui/api/user.utils.js b/dbrepo-ui/api/user.utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..094ca82926fdc616395edc33110c547eb35f57d7
--- /dev/null
+++ b/dbrepo-ui/api/user.utils.js
@@ -0,0 +1,17 @@
+class UserUtils {
+  hasReadAccess (access) {
+    if (!access) {
+      return false
+    }
+    return access.type === 'read' || this.hasWriteAccess(access)
+  }
+
+  hasWriteAccess (access) {
+    if (!access) {
+      return false
+    }
+    return access.type === 'write_own' || access.type === 'write_all'
+  }
+}
+
+export default new UserUtils()
diff --git a/dbrepo-ui/components/TableToolbar.vue b/dbrepo-ui/components/TableToolbar.vue
index 80a54b2813071910ed0c0b353acedcb73c99f93e..c11713dd33b36508a043d6112125d6f5a00838e0 100644
--- a/dbrepo-ui/components/TableToolbar.vue
+++ b/dbrepo-ui/components/TableToolbar.vue
@@ -53,8 +53,9 @@
 
 <script>
 import EditTuple from '@/components/dialogs/EditTuple'
-import { isResearcher } from '@/utils'
 import TableService from '@/api/table.service'
+import UserUtils from '@/api/user.utils'
+import DatabaseUtils from '@/api/database.utils'
 
 export default {
   components: {
@@ -101,43 +102,34 @@ export default {
       return this.$store.state.token
     },
     canAddTuple () {
-      if (!this.roles) {
-        return false
-      }
-      if (!this.isDataTab) {
+      if (!this.roles || !this.isDataTab) {
         return false
       }
-      return this.roles.includes('insert-table-data')
+      return UserUtils.hasWriteAccess(this.access) && this.roles.includes('insert-table-data')
     },
     canEditTuple () {
-      if (!this.roles) {
-        return false
-      }
-      if (!this.isDataTab) {
+      if (!this.roles || !this.isDataTab) {
         return false
       }
-      return this.roles.includes('insert-table-data')
+      return UserUtils.hasWriteAccess(this.access) && this.roles.includes('insert-table-data')
     },
     canDeleteTuple () {
-      if (!this.roles) {
-        return false
-      }
-      if (!this.isDataTab) {
+      if (!this.roles || !this.isDataTab) {
         return false
       }
-      return this.roles.includes('delete-table-data')
+      return UserUtils.hasWriteAccess(this.access) && this.roles.includes('delete-table-data')
     },
     canExecuteQuery () {
       if (!this.roles) {
         return false
       }
-      return this.roles.includes('execute-query')
+      return UserUtils.hasReadAccess(this.access) && this.roles.includes('execute-query')
     },
     canCreateView () {
       if (!this.user) {
         return false
       }
-      return this.roles.includes('create-database-view')
+      return DatabaseUtils.isOwner(this.database, this.user) && this.roles.includes('create-database-view')
     },
     canViewTableData () {
       /* view when database is public or when private: 1) view-table-data role present 2) access is at least read */
@@ -169,12 +161,6 @@ export default {
         return false
       }
       return this.database.creator.username === this.user.username
-    },
-    isResearcher () {
-      return isResearcher(this.user)
-    },
-    databaseTooltip () {
-      return this.database.is_public ? 'Public' : 'Private'
     }
   },
   methods: {
diff --git a/dbrepo-ui/components/dialogs/EditAccess.vue b/dbrepo-ui/components/dialogs/EditAccess.vue
index 1ba70270745992a29236b81d58b561d1cfc140b0..6048006744ea2c0b19a1ffa55e8669fedf7cfa87 100644
--- a/dbrepo-ui/components/dialogs/EditAccess.vue
+++ b/dbrepo-ui/components/dialogs/EditAccess.vue
@@ -26,6 +26,7 @@
                 v-if="!isModification"
                 v-model="modify.username"
                 :items="eligibleUsers"
+                :disabled="loadingUsers"
                 :loading="loadingUsers"
                 :rules="[v => !!v || $t('Required')]"
                 required
@@ -75,6 +76,7 @@
 <script>
 import DatabaseService from '@/api/database.service'
 import UserService from '@/api/user.service'
+
 export default {
   props: {
     username: {
@@ -199,9 +201,9 @@ export default {
     },
     revokeAccess () {
       this.loading = true
-      DatabaseService.revokeAccess(this.$route.params.container_id, this.$route.params.database_id, this.username)
+      DatabaseService.revokeAccess(this.$route.params.container_id, this.$route.params.database_id, this.modify.username)
         .then(() => {
-          this.$toast.success(`Successfully revoked access of ${this.username}`)
+          this.$toast.success(`Successfully revoked access of ${this.modify.username}`)
           this.$emit('close-dialog', { success: true })
         })
         .finally(() => {
@@ -210,9 +212,9 @@ export default {
     },
     modifyAccess () {
       this.loading = true
-      DatabaseService.modifyAccess(this.$route.params.container_id, this.$route.params.database_id, this.username, this.modify.type)
+      DatabaseService.modifyAccess(this.$route.params.container_id, this.$route.params.database_id, this.modify.username, this.modify.type)
         .then(() => {
-          this.$toast.success('Successfully modified access')
+          this.$toast.success(`Successfully modified access of ${this.modify.username}`)
           this.$emit('close-dialog', { success: true })
         })
         .finally(() => {
@@ -220,11 +222,10 @@ export default {
         })
     },
     giveAccess () {
-      const username = this.modify.username
       this.loading = true
-      DatabaseService.giveAccess(this.$route.params.container_id, this.$route.params.database_id, this.username, this.modify.type)
+      DatabaseService.giveAccess(this.$route.params.container_id, this.$route.params.database_id, this.modify.username, this.modify.type)
         .then(() => {
-          this.$toast.success(`Successfully gave ${username} access`)
+          this.$toast.success(`Successfully gave ${this.modify.username} access`)
           this.$emit('close-dialog', { success: true })
         })
         .finally(() => {
@@ -238,7 +239,7 @@ export default {
           this.users = users.filter(u => u.username !== this.database.creator.username)
         })
         .finally(() => {
-          this.loading = false
+          this.loadingUsers = false
         })
     },
     init () {
diff --git a/dbrepo-ui/components/dialogs/Persist.vue b/dbrepo-ui/components/dialogs/Persist.vue
index 704a8b342cb8e5914beea4484d87a8d9f8841689..bcc3068d66cdf52477d57b2ff0360a06c6ee42e4 100644
--- a/dbrepo-ui/components/dialogs/Persist.vue
+++ b/dbrepo-ui/components/dialogs/Persist.vue
@@ -4,10 +4,10 @@
       <v-card-title v-text="title" />
       <v-card-text>
         <v-alert
-          v-if="isSubset"
+          v-if="identifier.visibility === 'self'"
           border="left"
           color="info">
-          Choose an expressive subset title and describe what it produces.
+          Private datasets significantly impact the reusability of your research.
         </v-alert>
         <v-form v-model="formValid" autocomplete="off">
           <v-row dense>
@@ -20,6 +20,10 @@
                 :label="`${prefix} title *`"
                 :rules="[v => !!v || $t('Required')]"
                 required />
+            </v-col>
+          </v-row>
+          <v-row dense>
+            <v-col>
               <v-textarea
                 id="description"
                 v-model="identifier.description"
@@ -28,6 +32,20 @@
                 :label="`${prefix} description *`" />
             </v-col>
           </v-row>
+          <v-row v-if="isSubset" dense>
+            <v-col>
+              <v-select
+                v-model="identifier.visibility"
+                :items="visibilities"
+                item-text="name"
+                item-value="value"
+                label="Visibility *"
+                :hint="visibilityHint"
+                persistent-hint
+                :rules="[ v => !!v || $t('Required') ]"
+                required />
+            </v-col>
+          </v-row>
           <v-row dense>
             <v-col>
               <v-text-field
@@ -215,12 +233,17 @@ export default {
       loading: false,
       error: false, // XXX: `error` is never changed
       licenses: [],
+      visibilities: [
+        { name: 'Public', value: 'everyone' },
+        { name: 'Private', value: 'self' }
+      ],
       identifier: {
         cid: parseInt(this.$route.params.container_id),
         dbid: parseInt(this.$route.params.database_id),
         qid: parseInt(this.$route.params.query_id),
         title: null,
         description: null,
+        visibility: 'everyone',
         publisher: this.$config.defaultPublisher,
         publication_year: formatYearUTC(Date.now()),
         publication_month: formatMonthUTC(Date.now()),
@@ -326,6 +349,12 @@ export default {
       }
       return (title + ' identifier')
     },
+    visibilityHint () {
+      if (this.identifier.visibility === 'public') {
+        return 'The result set will be open access (world-readable)'
+      }
+      return 'The result set will be visible only to you'
+    },
     prefix () {
       if (this.isSubset) {
         return 'Subset'
diff --git a/dbrepo-ui/components/query/Results.vue b/dbrepo-ui/components/query/Results.vue
index 1f30ee22566bf88f9184576f86ffac03c42c8967..74a06f23e8b17e70409aac780278adcc3c100425 100644
--- a/dbrepo-ui/components/query/Results.vue
+++ b/dbrepo-ui/components/query/Results.vue
@@ -70,7 +70,7 @@ export default {
       }
       this.loading++
       if (this.type === 'query') {
-        QueryService.reExecuteQuery(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage)
+        QueryService.reExecuteQuery(this.$route.params.container_id, this.$route.params.database_id, id, 0, this.options.itemsPerPage)
           .then((result) => {
             this.mapResults(result)
             this.id = id
@@ -79,7 +79,7 @@ export default {
             this.loading--
           })
       } else {
-        QueryService.reExecuteView(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage)
+        QueryService.reExecuteView(this.$route.params.container_id, this.$route.params.database_id, id, 0, this.options.itemsPerPage)
           .then((result) => {
             this.mapResults(result)
             this.id = id
@@ -95,7 +95,7 @@ export default {
       }
       this.loading++
       if (this.type === 'query') {
-        QueryService.reExecuteQueryCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId)
+        QueryService.reExecuteQueryCount(this.$route.params.container_id, this.$route.params.database_id, id)
           .then((count) => {
             this.total = count
           })
@@ -103,7 +103,7 @@ export default {
             this.loading--
           })
       } else {
-        QueryService.reExecuteViewCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId)
+        QueryService.reExecuteViewCount(this.$route.params.container_id, this.$route.params.database_id, id)
           .then((count) => {
             this.total = count
           })
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
index e7cac6601311c7bf3711b7e26efef2c6333f615e..dcbe33966032f5b0c9bbfe9062d54ef259da85a9 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
@@ -11,7 +11,7 @@
       </v-toolbar-title>
       <v-spacer />
       <v-toolbar-title>
-        <v-btn v-if="!loadingQuery && !query.is_persisted && canWrite" :loading="loadingSave" class="mb-1" @click.stop="save">
+        <v-btn v-if="canPersistQuery" :loading="loadingSave" class="mb-1" @click.stop="save">
           <v-icon left>mdi-content-save-outline</v-icon> Save
         </v-btn>
         <v-btn v-if="query.is_persisted && !query.identifier && canWrite" class="mb-1 ml-2" color="primary" :disabled="!executionUTC" @click.stop="openDialog()">
@@ -104,7 +104,7 @@
       </v-card-title>
       <v-card-text>
         <v-alert
-          v-if="!loadingQuery && !query.is_persisted && canWrite"
+          v-if="canPersistQuery"
           border="left"
           color="info">
           Query is not yet saved in the query store, <a @click="save">save</a> it to view it later.
@@ -225,6 +225,7 @@ import Banner from '@/components/identifier/Banner'
 import DownloadButton from '@/components/identifier/DownloadButton'
 import { formatTimestampUTCLabel, formatDateUTC } from '@/utils'
 import QueryService from '@/api/query.service'
+import UserUtils from '@/api/user.utils'
 
 export default {
   name: 'QueryShow',
@@ -304,23 +305,11 @@ export default {
       }
       return this.query.result_hash
     },
-    config () {
-      if (this.token === null) {
-        return {
-          headers: {},
-          progress: false
-        }
-      }
-      return {
-        headers: { Authorization: `Bearer ${this.token}` },
-        progress: false
-      }
-    },
-    silentConfig () {
-      return {
-        headers: this.config.headers,
-        progress: false
+    canPersistQuery () {
+      if (!this.query || this.query.is_persisted) {
+        return false
       }
+      return UserUtils.hasReadAccess(this.access)
     },
     publisher () {
       if (this.database.publisher === null) {
@@ -350,10 +339,7 @@ export default {
       if (!this.access || !this.access.type) {
         return false
       }
-      if (this.access.type === 'write_own' || this.access.type === 'write_all') {
-        return true
-      }
-      return false
+      return this.access.type === 'write_own' || this.access.type === 'write_all'
     },
     publication () {
       if (this.query.identifier.publication_year && !this.query.identifier.publication_month && !this.query.identifier.publication_day) {
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/query/create.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/query/create.vue
index dd5c10a9e73cd3154a62dd00304457c9e333d5d9..f6864c8b031ed621d25b20b6b83cc6e6fc295680 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/query/create.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/query/create.vue
@@ -6,6 +6,8 @@
 </template>
 
 <script>
+import UserUtils from '@/api/user.utils'
+
 export default {
   data () {
     return {
@@ -31,11 +33,14 @@ export default {
     roles () {
       return this.$store.state.roles
     },
+    access () {
+      return this.$store.state.access
+    },
     canExecuteQuery () {
-      if (!this.roles) {
+      if (!this.roles || !this.access) {
         return false
       }
-      return this.roles.includes('execute-query')
+      return UserUtils.hasReadAccess(this.access) && this.roles.includes('execute-query')
     }
   }
 }
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue
index b0cbfc7b4520127c537cdff9e2ca207760b368b9..d2caacbd0d4240577285a4f82f76e9db4e88e703 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/import.vue
@@ -83,7 +83,7 @@
               <v-file-input
                 v-model="fileModel"
                 accept=".csv,.tsv"
-                hint="max. 100 GB file size"
+                hint="max. 2GB file size"
                 persistent-hint
                 show-size
                 label="CSV/TSV File" />
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/info.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/info.vue
index 8f7e4e6b53c0a7dd5594bed0fd4db8645beb72ba..171c0e219e5d4d5e861ff06a8ed8e57c4faf2b4d 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/info.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/info.vue
@@ -25,10 +25,10 @@
                 <v-skeleton-loader v-if="!table" type="text" class="skeleton-medium" />
               </v-list-item-content>
               <v-list-item-title class="mt-2">
-                Table Creator
+                Table Owner
               </v-list-item-title>
               <v-list-item-content>
-                <span v-if="table && table.creator">{{ formatCreator(table.creator) }} <span v-if="is_owner(table)" style="flex:none;">&nbsp;(you)</span></span>
+                <span v-if="table && table.creator">{{ formatCreator(table.owner) }} <span v-if="is_owner(table)" style="flex:none;">&nbsp;(you)</span></span>
                 <v-skeleton-loader v-if="!table" type="text" class="skeleton-medium" />
               </v-list-item-content>
               <v-list-item-title v-if="table && table.created" class="mt-2">
@@ -210,7 +210,7 @@ export default {
       if (!this.user) {
         return false
       }
-      return table.creator.username === this.user.username
+      return table.owner.username === this.user.username
     },
     consumerDetails () {
       if (!this.table) {
diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/import.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/import.vue
index a915657c3c5484e1cb150f35a200a0fdae6511cf..7f43332873c371001334fe57dc0eb4575367f803 100644
--- a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/import.vue
+++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/import.vue
@@ -139,7 +139,7 @@
               <v-file-input
                 v-model="fileModel"
                 accept=".csv,.tsv"
-                hint="max. 100 GB file size"
+                hint="max. 2 GB file size"
                 persistent-hint
                 show-size
                 label="File Upload (.csv/.tsv)" />
@@ -160,7 +160,7 @@
               <v-btn
                 class="mb-1"
                 :disabled="!fileModel"
-                :loading="loading"
+                :loading="loadingUpload || loadingAnalyse"
                 color="primary"
                 type="submit"
                 @click="uploadAndAnalyse">
@@ -174,7 +174,7 @@
         Table Schema
       </v-stepper-step>
       <v-stepper-content step="4">
-        <TableSchema :back="true" :error="error" :loading="loading" :columns="tableCreate.columns" @close="schemaClose" />
+        <TableSchema :back="true" :error="error" :loading="loadingImage" :columns="tableCreate.columns" @close="schemaClose" />
       </v-stepper-content>
       <v-stepper-step
         :complete="step > 5"
@@ -256,6 +256,9 @@ export default {
         skip_lines: 1
       },
       loading: false,
+      loadingUpload: false,
+      loadingAnalyse: false,
+      loadingImage: false,
       url: null,
       columns: [],
       newTableId: 42 // FIXME ???
@@ -316,7 +319,7 @@ export default {
       this.$refs.form.validate()
     },
     upload () {
-      this.loading = true
+      this.loadingUpload = true
       return new Promise((resolve, reject) => {
         MiddlewareService.upload(this.fileModel)
           .then((file) => {
@@ -327,12 +330,12 @@ export default {
             reject(error)
           })
           .finally(() => {
-            this.loading = false
+            this.loadingUpload = false
           })
       })
     },
     analyse () {
-      this.loading = true
+      this.loadingAnalyse = true
       AnalyseService.determineDataTypes(`/tmp/${this.file.filename}`)
         .then((analysis) => {
           const { columns } = analysis
@@ -348,10 +351,9 @@ export default {
             })
           this.tableImport.location = `/tmp/${this.file.filename}`
           this.step = 4
-          this.loading = false
         })
         .finally(() => {
-          this.loading = false
+          this.loadingAnalyse = false
         })
     },
     listTables () {
@@ -371,18 +373,24 @@ export default {
         return
       }
       this.validStep4 = true
-      this.step = 5
       this.createTable()
     },
     setOthers (column) {
       column.null_allowed = false
       column.unique = true
     },
-    async loadDateFormats () {
-      this.loading = true
-      const res = await ContainerService.findOne(this.$route.params.container_id)
-      this.dateFormats = await ContainerService.findImage(res.image.id).date_formats
-      this.loading = true
+    loadDateFormats () {
+      this.loadingImage = true
+      ContainerService.findOne(this.$route.params.container_id)
+        .then((container) => {
+          ContainerService.findImage(container.image.id)
+            .then((image) => {
+              this.dateFormats = image.date_formats
+            })
+        })
+        .finally(() => {
+          this.loadingImage = true
+        })
     },
     createTable () {
       /* make enum values to array */
diff --git a/dbrepo-ui/pages/signup.vue b/dbrepo-ui/pages/signup.vue
index f684bf48dc46e9c5449f1528bf0163b3147f1ccb..469907ca6a276796460295e3ec5dcf06ceefd0ac 100644
--- a/dbrepo-ui/pages/signup.vue
+++ b/dbrepo-ui/pages/signup.vue
@@ -36,7 +36,8 @@
                 required
                 name="username"
                 :rules="[v => !!v || $t('Required'),
-                         v => /^[a-z0-9]{3,}$/.test(v) || $t('Only lowercase letters, min. 3 length')]"
+                         v => /^[a-z0-9]{3,}$/.test(v) || $t('Only lowercase letters, min. 3 length'),
+                         v => !usernames.includes(v) || $t('This username is already taken')]"
                 hint="e.g. mmustermann"
                 label="Username *" />
             </v-col>
@@ -109,6 +110,8 @@ export default {
   data () {
     return {
       loading: false,
+      loadingUsers: false,
+      usernames: [],
       error: false, // XXX: `error` is never changed
       valid: false,
       password2: null,
@@ -132,6 +135,9 @@ export default {
       return this.$config.mailVerify
     }
   },
+  mounted () {
+    this.loadUsers()
+  },
   methods: {
     submit () {
       this.$refs.form.validate()
@@ -147,6 +153,16 @@ export default {
         .catch(() => {
           this.loading = false
         })
+    },
+    loadUsers () {
+      this.loadingUsers = true
+      UserService.findAll()
+        .then((users) => {
+          this.usernames = users.map(u => u.username)
+        })
+        .finally(() => {
+          this.loadingUsers = false
+        })
     }
   }
 }
diff --git a/dbrepo-user-service/pom.xml b/dbrepo-user-service/pom.xml
index 8a5225c3fb3ded3fb1a5d5e42b60daca892ddce5..471c9323648323786875b697a08aa3402f29e778 100644
--- a/dbrepo-user-service/pom.xml
+++ b/dbrepo-user-service/pom.xml
@@ -226,6 +226,7 @@
                         <exclude>at/tuwien/handlers/**/*</exclude>
                         <exclude>at/tuwien/exception/**/*</exclude>
                         <exclude>at/tuwien/config/**/*</exclude>
+                        <exclude>at/tuwien/auth/**/*</exclude>
                         <exclude>**/FdaUserServiceApplication.class</exclude>
                     </excludes>
                 </configuration>
diff --git a/dbrepo-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java b/dbrepo-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java
index 08e2b0d7a36b8b02a9096b8f3c875049c4757cfc..3bdd01e898403c563ae0928004ea34c248c441ea 100644
--- a/dbrepo-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java
+++ b/dbrepo-user-service/rest-service/src/main/java/at/tuwien/endpoint/UserEndpoint.java
@@ -67,15 +67,21 @@ public class UserEndpoint {
 
     @PostMapping
     @Transactional
+    @PreAuthorize("!isAuthenticated()")
     @Timed(value = "user.create", description = "Time needed to create a user in the metadata database")
     @Operation(summary = "Create user")
     public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody SignupRequestDto data)
             throws UserNotFoundException, RemoteUnavailableException, RealmNotFoundException,
-            UserAlreadyExistsException, RoleNotFoundException {
+            UserAlreadyExistsException, RoleNotFoundException, UserEmailAlreadyExistsException {
         log.debug("endpoint create a user, data={}", data);
+        /* check */
         final Realm realm = realmService.find("dbrepo");
         final Role role = roleService.find(authenticationConfig.getDefaultRole());
-        final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data, realm, role));
+        userService.validateUsernameNotExists(data.getUsername());
+        userService.validateEmailNotExists(data.getEmail());
+        /* create */
+        final User user = userService.create(data, realm, role);
+        final UserBriefDto dto = userMapper.userToUserBriefDto(user);
         log.trace("create user resulted in dto {}", dto);
         return ResponseEntity.status(HttpStatus.CREATED)
                 .body(dto);
@@ -83,17 +89,27 @@ public class UserEndpoint {
 
     @GetMapping("/{id}")
     @Transactional
-    @PreAuthorize("isAuthenticated()")
+    @PreAuthorize("isAuthenticated() or hasAuthority('find-user')")
     @Timed(value = "user.info", description = "Time needed to get information of a user in the metadata database")
     @Operation(summary = "Get a user info", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<UserDto> find(@NotNull @PathVariable("id") String id,
                                         @NotNull Principal principal)
-            throws UserNotFoundException {
+            throws UserNotFoundException, NotAllowedException {
         log.debug("endpoint find a user, id={}, principal={}", id, principal);
-        final UserDto dto = userMapper.userToUserDto(userService.find(UUID.fromString(id)));
-        log.trace("find user resulted in dto {}", dto);
-        return ResponseEntity.ok()
-                .body(dto);
+        /* check */
+        final User user = userService.find(UUID.fromString(id));
+        final UserDto dto = userMapper.userToUserDto(user);
+        if (user.getUsername().equals(principal.getName())) {
+            log.trace("find user resulted in dto {}", dto);
+            return ResponseEntity.ok()
+                    .body(dto);
+        } else if (User.hasRole(principal, "find-user")) {
+            log.trace("find user resulted in dto {}", dto);
+            return ResponseEntity.ok()
+                    .body(dto);
+        }
+        log.error("Failed to find user: no authority and not the current logged-in user");
+        throw new NotAllowedException("Failed to find user: no authority");
     }
 
     @PutMapping("/{id}")
@@ -106,12 +122,14 @@ public class UserEndpoint {
                                           @NotNull Principal principal)
             throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
         log.debug("endpoint modify a user, id={}, data={}, principal={}", id, data, principal);
+        /* check */
         final User user = userService.find(UUID.fromString(id));
         if (!user.equalsPrincipal(principal)) {
             log.error("Failed to modify user: attempting to modify other user");
             throw new ForeignUserException("Failed to modify user: attempting to modify other user");
         }
-        final UserDto dto = userMapper.userToUserDto(userService.modify(user.getId(), data, principal));
+        /* modify */
+        final UserDto dto = userMapper.userToUserDto(userService.modify(user.getId(), data));
         log.trace("modify user resulted in dto {}", dto);
         return ResponseEntity.status(HttpStatus.ACCEPTED)
                 .body(dto);
@@ -127,12 +145,14 @@ public class UserEndpoint {
                                          @NotNull Principal principal)
             throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
         log.debug("endpoint modify a user theme, id={}, data={}, principal={}", id, data, principal);
+        /* check */
         final User user = userService.find(UUID.fromString(id));
         if (!user.equalsPrincipal(principal)) {
             log.error("Failed to modify user: attempting to modify other user");
             throw new ForeignUserException("Failed to modify user: attempting to modify other user");
         }
-        final UserDto dto = userMapper.userToUserDto(userService.toggleTheme(user.getId(), data, principal));
+        /* modify theme */
+        final UserDto dto = userMapper.userToUserDto(userService.toggleTheme(user.getId(), data));
         log.trace("modify user theme resulted in dto {}", dto);
         return ResponseEntity.accepted()
                 .body(dto);
@@ -148,12 +168,14 @@ public class UserEndpoint {
                                             @NotNull Principal principal)
             throws UserNotFoundException, ForeignUserException {
         log.debug("endpoint modify a user password, id={}, data={}, principal={}", id, data, principal);
+        /* check */
         final User user = userService.find(UUID.fromString(id));
         if (!user.equalsPrincipal(principal)) {
             log.error("Failed to modify user: attempting to modify other user");
             throw new ForeignUserException("Failed to modify user: attempting to modify other user");
         }
-        final UserDto dto = userMapper.userToUserDto(userService.updatePassword(user.getId(), data, principal));
+        /* modify password */
+        final UserDto dto = userMapper.userToUserDto(userService.updatePassword(user.getId(), data));
         log.trace("updated user password resulted in dto {}", dto);
         return ResponseEntity.accepted()
                 .body(dto);
diff --git a/dbrepo-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java b/dbrepo-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java
index b5d0c1658bf704de3856a2321cdf4d2057412c66..c4f7c69f64bfcc65bcedf508f16cccb09ab4edf4 100644
--- a/dbrepo-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java
+++ b/dbrepo-user-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java
@@ -15,90 +15,13 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
 @ControllerAdvice
 public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
 
-    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
-    @ExceptionHandler(AmqpException.class)
-    public ResponseEntity<ApiErrorDto> handle(AmqpException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.NOT_ACCEPTABLE)
-                .message(e.getLocalizedMessage())
-                .code("error.query.amqp")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
-    @ExceptionHandler(ColumnParseException.class)
-    public ResponseEntity<ApiErrorDto> handle(ColumnParseException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.EXPECTATION_FAILED)
-                .message(e.getLocalizedMessage())
-                .code("error.query.columnparse")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(ContainerNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(ContainerNotFoundException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.NOT_FOUND)
-                .message(e.getLocalizedMessage())
-                .code("error.query.containernotfound")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
     @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
-    @ExceptionHandler(DatabaseConnectionException.class)
-    public ResponseEntity<ApiErrorDto> handle(DatabaseConnectionException e, WebRequest request) {
+    @ExceptionHandler(ForeignUserException.class)
+    public ResponseEntity<ApiErrorDto> handle(ForeignUserException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.METHOD_NOT_ALLOWED)
                 .message(e.getLocalizedMessage())
-                .code("error.query.databaseconnection")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(DatabaseNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(DatabaseNotFoundException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.NOT_FOUND)
-                .message(e.getLocalizedMessage())
-                .code("error.query.databasenotfound")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ExceptionHandler(FileStorageException.class)
-    public ResponseEntity<ApiErrorDto> handle(FileStorageException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.BAD_REQUEST)
-                .message(e.getLocalizedMessage())
-                .code("error.query.filestore")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(IdentifierNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(IdentifierNotFoundException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.NOT_FOUND)
-                .message(e.getLocalizedMessage())
-                .code("error.query.identifiernotfound")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.CONFLICT)
-    @ExceptionHandler(ImageNotSupportedException.class)
-    public ResponseEntity<ApiErrorDto> handle(ImageNotSupportedException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.CONFLICT)
-                .message(e.getLocalizedMessage())
-                .code("error.query.imagenotsupported")
+                .code("error.user.foreignpermission")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
@@ -109,139 +32,84 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.METHOD_NOT_ALLOWED)
                 .message(e.getLocalizedMessage())
-                .code("error.query.permission")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ExceptionHandler(PaginationException.class)
-    public ResponseEntity<ApiErrorDto> handle(PaginationException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.BAD_REQUEST)
-                .message(e.getLocalizedMessage())
-                .code("error.query.pagination")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.CONFLICT)
-    @ExceptionHandler(QueryAlreadyPersistedException.class)
-    public ResponseEntity<ApiErrorDto> handle(QueryAlreadyPersistedException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.CONFLICT)
-                .message(e.getLocalizedMessage())
-                .code("error.query.alreadypersisted")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ExceptionHandler(QueryMalformedException.class)
-    public ResponseEntity<ApiErrorDto> handle(QueryMalformedException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.BAD_REQUEST)
-                .message(e.getLocalizedMessage())
-                .code("error.query.malformed")
+                .code("error.user.permission")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
     @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(QueryNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(QueryNotFoundException e, WebRequest request) {
+    @ExceptionHandler(RealmNotFoundException.class)
+    public ResponseEntity<ApiErrorDto> handle(RealmNotFoundException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.NOT_FOUND)
                 .message(e.getLocalizedMessage())
-                .code("error.query.notfound")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
-    @ExceptionHandler(QueryStoreException.class)
-    public ResponseEntity<ApiErrorDto> handle(QueryStoreException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.GATEWAY_TIMEOUT)
-                .message(e.getLocalizedMessage())
-                .code("error.query.store")
-                .build();
-        return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
-    }
-
-    @ResponseStatus(HttpStatus.BAD_REQUEST)
-    @ExceptionHandler(SortException.class)
-    public ResponseEntity<ApiErrorDto> handle(SortException e, WebRequest request) {
-        final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.BAD_REQUEST)
-                .message(e.getLocalizedMessage())
-                .code("error.query.sort")
+                .code("error.user.realmnotfound")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
-    @ResponseStatus(HttpStatus.LOCKED)
-    @ExceptionHandler(TableMalformedException.class)
-    public ResponseEntity<ApiErrorDto> handle(TableMalformedException e, WebRequest request) {
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    @ExceptionHandler(RemoteUnavailableException.class)
+    public ResponseEntity<ApiErrorDto> handle(RemoteUnavailableException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.LOCKED)
+                .status(HttpStatus.NO_CONTENT)
                 .message(e.getLocalizedMessage())
-                .code("error.query.tablemalformed")
+                .code("error.user.remoteunavailable")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
     @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(TableNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(TableNotFoundException e, WebRequest request) {
+    @ExceptionHandler(RoleNotFoundException.class)
+    public ResponseEntity<ApiErrorDto> handle(RoleNotFoundException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.NOT_FOUND)
                 .message(e.getLocalizedMessage())
-                .code("error.query.tablenotfound")
+                .code("error.user.rolenotfound")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
     @ResponseStatus(HttpStatus.CONFLICT)
-    @ExceptionHandler(TupleDeleteException.class)
-    public ResponseEntity<ApiErrorDto> handle(TupleDeleteException e, WebRequest request) {
+    @ExceptionHandler(UserAlreadyExistsException.class)
+    public ResponseEntity<ApiErrorDto> handle(UserAlreadyExistsException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.CONFLICT)
                 .message(e.getLocalizedMessage())
-                .code("error.query.tupledelete")
+                .code("error.user.alreadyexists")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
     @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(UserNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(UserNotFoundException e, WebRequest request) {
+    @ExceptionHandler(UserAttributeNotFoundException.class)
+    public ResponseEntity<ApiErrorDto> handle(UserAttributeNotFoundException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.NOT_FOUND)
                 .message(e.getLocalizedMessage())
-                .code("error.query.usernotfound")
+                .code("error.user.attributenotfound")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
-    @ResponseStatus(HttpStatus.LOCKED)
-    @ExceptionHandler(ViewMalformedException.class)
-    public ResponseEntity<ApiErrorDto> handle(ViewMalformedException e, WebRequest request) {
+    @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
+    @ExceptionHandler(UserEmailAlreadyExistsException.class)
+    public ResponseEntity<ApiErrorDto> handle(UserEmailAlreadyExistsException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
-                .status(HttpStatus.LOCKED)
+                .status(HttpStatus.EXPECTATION_FAILED)
                 .message(e.getLocalizedMessage())
-                .code("error.query.viewmalformed")
+                .code("error.user.emailexists")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
 
     @ResponseStatus(HttpStatus.NOT_FOUND)
-    @ExceptionHandler(ViewNotFoundException.class)
-    public ResponseEntity<ApiErrorDto> handle(ViewNotFoundException e, WebRequest request) {
+    @ExceptionHandler(UserNotFoundException.class)
+    public ResponseEntity<ApiErrorDto> handle(UserNotFoundException e, WebRequest request) {
         final ApiErrorDto response = ApiErrorDto.builder()
                 .status(HttpStatus.NOT_FOUND)
                 .message(e.getLocalizedMessage())
-                .code("error.query.viewnotfound")
+                .code("error.user.notfound")
                 .build();
         return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus());
     }
diff --git a/dbrepo-user-service/rest-service/src/test/java/at/tuwien/endpoint/UserEndpointUnitTest.java b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/endpoint/UserEndpointUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..df7a391957fe6cc8d33c21d76aa808dbe21b3bcc
--- /dev/null
+++ b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/endpoint/UserEndpointUnitTest.java
@@ -0,0 +1,466 @@
+package at.tuwien.endpoint;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.api.auth.SignupRequestDto;
+import at.tuwien.api.user.*;
+import at.tuwien.config.AuthenticationConfig;
+import at.tuwien.entities.user.Realm;
+import at.tuwien.entities.user.Role;
+import at.tuwien.entities.user.User;
+import at.tuwien.exception.*;
+import at.tuwien.service.RealmService;
+import at.tuwien.service.RoleService;
+import at.tuwien.service.UserService;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.security.Principal;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class UserEndpointUnitTest extends BaseUnitTest {
+
+    @MockBean
+    private UserService userService;
+
+    @MockBean
+    private RealmService realmService;
+
+    @MockBean
+    private RoleService roleService;
+
+    @MockBean
+    private AuthenticationConfig authenticationConfig;
+
+    @Autowired
+    private UserEndpoint userEndpoint;
+
+    @BeforeEach
+    public void beforeEach() {
+        when(authenticationConfig.getDefaultRole())
+                .thenReturn("default-researcher-roles");
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void findAll_anonymous_succeeds() {
+
+        /* test */
+        findAll_generic();
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void findAll_noRole_succeeds() {
+
+        /* test */
+        findAll_generic();
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void create_anonymous_succeeds() throws UserNotFoundException, UserEmailAlreadyExistsException,
+            RealmNotFoundException, RoleNotFoundException, RemoteUnavailableException, UserAlreadyExistsException {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .email(USER_1_EMAIL)
+                .username(USER_1_USERNAME)
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        create_generic(USER_1, REALM_DBREPO, ROLE_DEFAULT_RESEARCHER_ROLES, request);
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void create_realmNotFound_fails() {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .email(USER_1_EMAIL)
+                .username(USER_1_USERNAME)
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(RealmNotFoundException.class, () -> {
+            create_generic(USER_1, null, ROLE_DEFAULT_RESEARCHER_ROLES, request);
+        });
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void create_roleNotFound_fails() {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .email(USER_1_EMAIL)
+                .username(USER_1_USERNAME)
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(RoleNotFoundException.class, () -> {
+            create_generic(USER_1, REALM_DBREPO, null, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void create_isAuthenticated_fails() {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .email(USER_2_EMAIL)
+                .username(USER_2_USERNAME)
+                .password(USER_2_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            create_generic(USER_2, REALM_DBREPO, ROLE_DEFAULT_RESEARCHER_ROLES, request);
+        });
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void find_anonymous_fails() {
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            find_generic(USER_1_ID.toString(), USER_1, null);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void find_self_succeeds() throws UserNotFoundException, NotAllowedException {
+
+        /* test */
+        find_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL);
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void find_foreign_fails() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            find_generic(USER_2_ID.toString(), USER_2, USER_1_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_4_USERNAME, authorities = {"find-user"})
+    public void find_hasRoleForeign_succeeds() {
+
+        /* test */
+        assertThrows(NotAllowedException.class, () -> {
+            find_generic(USER_2_ID.toString(), USER_2, USER_4_PRINCIPAL);
+        });
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void modify_anonymous_fails() {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_1_FIRSTNAME)
+                .lastname(USER_1_LASTNAME)
+                .affiliation(USER_1_AFFILIATION)
+                .orcid(USER_1_ORCID)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            modify_generic(USER_1_ID.toString(), USER_1, null, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void modify_noRole_fails() {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_1_FIRSTNAME)
+                .lastname(USER_1_LASTNAME)
+                .affiliation(USER_1_AFFILIATION)
+                .orcid(USER_1_ORCID)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            modify_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_2_USERNAME, authorities = {"modify-user-information"})
+    public void modify_hasRoleForeign_fails() {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_1_FIRSTNAME)
+                .lastname(USER_1_LASTNAME)
+                .affiliation(USER_1_AFFILIATION)
+                .orcid(USER_1_ORCID)
+                .build();
+
+        /* test */
+        assertThrows(ForeignUserException.class, () -> {
+            modify_generic(USER_1_ID.toString(), USER_1, USER_2_PRINCIPAL, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-user-information"})
+    public void modify_succeeds() throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_1_FIRSTNAME)
+                .lastname(USER_1_LASTNAME)
+                .affiliation(USER_1_AFFILIATION)
+                .orcid(USER_1_ORCID)
+                .build();
+
+        /* test */
+        modify_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL, request);
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void theme_anonymous_fails() {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(USER_1_THEME_DARK)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            theme_generic(USER_1_ID.toString(), USER_1, null, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void theme_noRole_fails() {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(USER_1_THEME_DARK)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            theme_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_2_USERNAME, authorities = {"modify-user-theme"})
+    public void theme_hasRoleForeign_fails() {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(USER_1_THEME_DARK)
+                .build();
+
+        /* test */
+        assertThrows(ForeignUserException.class, () -> {
+            theme_generic(USER_1_ID.toString(), USER_1, USER_2_PRINCIPAL, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME, authorities = {"modify-user-theme"})
+    public void theme_succeeds() throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(USER_1_THEME_DARK)
+                .build();
+
+        /* test */
+        theme_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL, request);
+    }
+
+    @Test
+    @WithAnonymousUser
+    public void password_anonymous_fails() {
+        final UserPasswordDto request = UserPasswordDto.builder()
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(AccessDeniedException.class, () -> {
+            password_generic(USER_1_ID.toString(), USER_1, null, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_2_USERNAME)
+    public void password_noRoleForeign_fails() {
+        final UserPasswordDto request = UserPasswordDto.builder()
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(ForeignUserException.class, () -> {
+            password_generic(USER_1_ID.toString(), USER_1, USER_2_PRINCIPAL, request);
+        });
+    }
+
+    @Test
+    @WithMockUser(username = USER_1_USERNAME)
+    public void password_succeeds() throws UserNotFoundException, ForeignUserException {
+        final UserPasswordDto request = UserPasswordDto.builder()
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        password_generic(USER_1_ID.toString(), USER_1, USER_1_PRINCIPAL, request);
+    }
+
+    /* ################################################################################################### */
+    /* ## GENERIC TEST CASES                                                                            ## */
+    /* ################################################################################################### */
+
+    protected void findAll_generic() {
+
+        /* mock */
+        when(userService.findAll())
+                .thenReturn(List.of(USER_1, USER_2));
+
+        /* test */
+        final ResponseEntity<List<UserBriefDto>> response = userEndpoint.findAll();
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final List<UserBriefDto> body = response.getBody();
+        assertNotNull(body);
+        assertEquals(2, body.size());
+    }
+
+    protected void create_generic(User user, Realm realm, Role role, SignupRequestDto data)
+            throws UserNotFoundException, UserEmailAlreadyExistsException, RealmNotFoundException,
+            RoleNotFoundException, RemoteUnavailableException, UserAlreadyExistsException {
+
+        /* mock */
+        if (realm != null) {
+            when(realmService.find("dbrepo"))
+                    .thenReturn(realm);
+        } else {
+            doThrow(RealmNotFoundException.class)
+                    .when(realmService)
+                    .find("dbrepo");
+        }
+        if (role != null) {
+            when(roleService.find("default-researcher-roles"))
+                    .thenReturn(role);
+        } else {
+            doThrow(RoleNotFoundException.class)
+                    .when(roleService)
+                    .find("default-researcher-roles");
+        }
+        if (user != null) {
+            when(userService.create(data, realm, role))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .create(data, realm, role);
+        }
+
+        /* test */
+        final ResponseEntity<UserBriefDto> response = userEndpoint.create(data);
+        assertEquals(HttpStatus.CREATED, response.getStatusCode());
+        final UserBriefDto body = response.getBody();
+        assertNotNull(body);
+    }
+
+    protected void find_generic(String id, User user, Principal principal) throws UserNotFoundException,
+            NotAllowedException {
+
+        /* mock */
+        if (user != null) {
+            when(userService.find(UUID.fromString(id)))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .find(UUID.fromString(id));
+        }
+
+        /* test */
+        final ResponseEntity<UserDto> response = userEndpoint.find(id, principal);
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+        final UserDto body = response.getBody();
+        assertNotNull(body);
+    }
+
+    protected void modify_generic(String id, User user, Principal principal, UserUpdateDto data)
+            throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
+
+        /* mock */
+        if (user != null) {
+            when(userService.find(UUID.fromString(id)))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .find(UUID.fromString(id));
+        }
+        when(userService.modify(UUID.fromString(id), data))
+                .thenReturn(user);
+
+        /* test */
+        final ResponseEntity<UserDto> response = userEndpoint.modify(id, data, principal);
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        final UserDto body = response.getBody();
+        assertNotNull(body);
+    }
+
+    protected void theme_generic(String id, User user, Principal principal, UserThemeSetDto data)
+            throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException {
+
+        /* mock */
+        if (user != null) {
+            when(userService.find(UUID.fromString(id)))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .find(UUID.fromString(id));
+        }
+        when(userService.toggleTheme(UUID.fromString(id), data))
+                .thenReturn(user);
+
+        /* test */
+        final ResponseEntity<UserDto> response = userEndpoint.theme(id, data, principal);
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        final UserDto body = response.getBody();
+        assertNotNull(body);
+    }
+
+    protected void password_generic(String id, User user, Principal principal, UserPasswordDto data)
+            throws UserNotFoundException, ForeignUserException {
+
+        /* mock */
+        if (user != null) {
+            when(userService.find(UUID.fromString(id)))
+                    .thenReturn(user);
+        } else {
+            doThrow(UserNotFoundException.class)
+                    .when(userService)
+                    .find(UUID.fromString(id));
+        }
+        when(userService.updatePassword(UUID.fromString(id), data))
+                .thenReturn(user);
+
+        /* test */
+        final ResponseEntity<UserDto> response = userEndpoint.password(id, data, principal);
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        final UserDto body = response.getBody();
+        assertNotNull(body);
+    }
+}
diff --git a/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RealmServiceIntegrationTest.java b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RealmServiceIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..53822947511046bfdc258e81fb11f0af22b3a697
--- /dev/null
+++ b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RealmServiceIntegrationTest.java
@@ -0,0 +1,57 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.entities.user.Realm;
+import at.tuwien.exception.RealmNotFoundException;
+import at.tuwien.repository.jpa.RealmRepository;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class RealmServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private RealmRepository realmRepository;
+
+    @Autowired
+    private RealmService realmService;
+
+    @BeforeEach
+    public void beforeEach() {
+        realmRepository.save(REALM_DBREPO);
+    }
+
+    @Test
+    public void find_succeeds() throws RealmNotFoundException {
+
+        /* test */
+        final Realm response = realmService.find(REALM_DBREPO_NAME);
+        assertNotNull(response);
+        assertEquals(REALM_DBREPO_ID, response.getId());
+        assertEquals(REALM_DBREPO_NAME, response.getName());
+        assertEquals(REALM_DBREPO_ENABLED, response.getEnabled());
+    }
+
+    @Test
+    public void find_fails() {
+
+        /* test */
+        assertThrows(RealmNotFoundException.class, () -> {
+            realmService.find("shadow");
+        });
+    }
+}
diff --git a/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RoleServiceIntegrationTest.java b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RoleServiceIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cc1cd90399afa2eb9757625c6afa879de77a0c3
--- /dev/null
+++ b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/RoleServiceIntegrationTest.java
@@ -0,0 +1,64 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.entities.user.Realm;
+import at.tuwien.entities.user.Role;
+import at.tuwien.exception.RealmNotFoundException;
+import at.tuwien.exception.RoleNotFoundException;
+import at.tuwien.repository.jpa.RealmRepository;
+import at.tuwien.repository.jpa.RoleRepository;
+import at.tuwien.repository.jpa.UserRepository;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class RoleServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private RoleRepository roleRepository;
+
+    @Autowired
+    private RoleService roleService;
+
+    @BeforeEach
+    public void beforeEach() {
+        userRepository.save(USER_1_SIMPLE);
+        roleRepository.save(ROLE_DEFAULT_RESEARCHER_ROLES);
+    }
+
+    @Test
+    public void find_succeeds() throws RoleNotFoundException {
+
+        /* test */
+        final Role response = roleService.find(ROLE_DEFAULT_RESEARCHER_ROLES_NAME);
+        assertNotNull(response);
+        assertEquals(ROLE_DEFAULT_RESEARCHER_ROLES_ID, response.getId());
+        assertEquals(ROLE_DEFAULT_RESEARCHER_ROLES_NAME, response.getName());
+    }
+
+    @Test
+    public void find_fails() {
+
+        /* test */
+        assertThrows(RoleNotFoundException.class, () -> {
+            roleService.find("1role2rulethemall");
+        });
+    }
+}
diff --git a/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserAttributeServiceIntegrationTest.java b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserAttributeServiceIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..70b28170937e8de1a288ddcd7da2369e61f75d08
--- /dev/null
+++ b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserAttributeServiceIntegrationTest.java
@@ -0,0 +1,100 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.entities.user.Role;
+import at.tuwien.entities.user.UserAttribute;
+import at.tuwien.exception.RoleNotFoundException;
+import at.tuwien.exception.UserAttributeNotFoundException;
+import at.tuwien.repository.jpa.RoleRepository;
+import at.tuwien.repository.jpa.UserAttributeRepository;
+import at.tuwien.repository.jpa.UserRepository;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class UserAttributeServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private UserAttributeRepository userAttributeRepository;
+
+    @Autowired
+    private UserAttributeService userAttributeService;
+
+    @BeforeEach
+    public void beforeEach() {
+        userRepository.save(USER_1_SIMPLE);
+        userAttributeRepository.saveAll(USER_1_ATTRIBUTES);
+        userRepository.save(USER_2_SIMPLE);
+    }
+
+    @Test
+    public void find_succeeds() throws UserAttributeNotFoundException {
+
+        /* test */
+        final UserAttribute response = userAttributeService.find(USER_1_ID, "theme_dark");
+        assertNotNull(response);
+    }
+
+    @Test
+    public void find_fails() {
+
+        /* test */
+        assertThrows(UserAttributeNotFoundException.class, () -> {
+            userAttributeService.find(USER_2_ID, "theme_dark");
+        });
+    }
+
+    @Test
+    public void create_succeeds() {
+        final UserAttribute request = UserAttribute.builder()
+                .id(UUID.randomUUID())
+                .userId(USER_2_ID)
+                .name("debug")
+                .value("yes")
+                .build();
+
+        /* test */
+        final UserAttribute response = userAttributeService.create(request);
+        assertNotNull(response);
+        assertEquals("debug", response.getName());
+        assertEquals("yes", response.getValue());
+    }
+
+    @Test
+    public void update_succeeds() throws UserAttributeNotFoundException {
+
+        /* test */
+        final UserAttribute response = userAttributeService.update(USER_1_ID, "affiliation", "NASA");
+        assertNotNull(response);
+        assertEquals("affiliation", response.getName());
+        assertEquals("NASA", response.getValue());
+    }
+
+    @Test
+    public void update_fails() {
+
+        /* test */
+        assertThrows(UserAttributeNotFoundException.class, () -> {
+            userAttributeService.update(USER_2_ID, "affiliation", "NASA");
+        });
+    }
+}
diff --git a/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..52cb9f38f1b62fa33687549e68f6618ba451d2bb
--- /dev/null
+++ b/dbrepo-user-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java
@@ -0,0 +1,246 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.api.auth.SignupRequestDto;
+import at.tuwien.api.user.UserPasswordDto;
+import at.tuwien.api.user.UserThemeSetDto;
+import at.tuwien.api.user.UserUpdateDto;
+import at.tuwien.entities.user.Role;
+import at.tuwien.entities.user.User;
+import at.tuwien.entities.user.UserAttribute;
+import at.tuwien.exception.*;
+import at.tuwien.repository.jpa.RealmRepository;
+import at.tuwien.repository.jpa.RoleRepository;
+import at.tuwien.repository.jpa.UserRepository;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Log4j2
+@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class UserServiceIntegrationTest extends BaseUnitTest {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private RealmRepository realmRepository;
+
+    @Autowired
+    private RoleRepository roleRepository;
+
+    @Autowired
+    private UserService userService;
+
+    @BeforeEach
+    public void beforeEach() {
+        realmRepository.save(REALM_DBREPO);
+        userRepository.save(USER_1);
+        roleRepository.save(ROLE_DEFAULT_RESEARCHER_ROLES);
+    }
+
+    @Test
+    public void findAll_succeeds() {
+
+        /* test */
+        final List<User> response = userService.findAll();
+        assertEquals(1, response.size());
+    }
+
+    @Test
+    public void create_succeeds() throws UserAlreadyExistsException {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .username(USER_2_USERNAME)
+                .password(USER_2_PASSWORD)
+                .email(USER_2_EMAIL)
+                .build();
+
+        /* test */
+        final User response = userService.create(request, REALM_DBREPO, ROLE_DEFAULT_RESEARCHER_ROLES);
+        assertEquals(1, response.getRoles().size());
+        final Role role = response.getRoles().get(0);
+        assertEquals(ROLE_DEFAULT_RESEARCHER_ROLES_ID, role.getId());
+        assertEquals(ROLE_DEFAULT_RESEARCHER_ROLES_NAME, role.getName());
+    }
+
+    @Test
+    public void create_nonUniqueUsername_fails() {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .username(USER_1_USERNAME)
+                .password(USER_2_PASSWORD)
+                .email(USER_2_EMAIL)
+                .build();
+
+        /* test */
+        assertThrows(DataIntegrityViolationException.class, () -> {
+            userService.create(request, REALM_DBREPO, ROLE_DEFAULT_RESEARCHER_ROLES);
+        });
+    }
+
+    @Test
+    public void create_nonUniqueEmail_fails() {
+        final SignupRequestDto request = SignupRequestDto.builder()
+                .username(USER_2_USERNAME)
+                .password(USER_2_PASSWORD)
+                .email(USER_1_EMAIL)
+                .build();
+
+        /* test */
+        assertThrows(DataIntegrityViolationException.class, () -> {
+            userService.create(request, REALM_DBREPO, ROLE_DEFAULT_RESEARCHER_ROLES);
+        });
+    }
+
+    @Test
+    @Transactional
+    public void modify_succeeds() throws UserNotFoundException, UserAttributeNotFoundException {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_1_FIRSTNAME)
+                .lastname(USER_1_LASTNAME)
+                .affiliation("NASA")
+                .orcid(null)
+                .build();
+
+        /* test */
+        final User response = userService.modify(USER_1_ID, request);
+        assertEquals(USER_1_ID, response.getId());
+        assertEquals(USER_1_FIRSTNAME, response.getFirstname());
+        assertEquals(USER_1_LASTNAME, response.getLastname());
+        assertEquals(3, response.getAttributes().size());
+        final Optional<UserAttribute> affiliation = response.getAttributes().stream().filter(a -> a.getName().equals("affiliation")).findFirst();
+        assertTrue(affiliation.isPresent());
+        assertEquals("NASA", affiliation.get().getValue());
+        final Optional<UserAttribute> orcid = response.getAttributes().stream().filter(a -> a.getName().equals("orcid")).findFirst();
+        assertTrue(orcid.isPresent());
+        assertNull(orcid.get().getValue());
+    }
+
+    @Test
+    public void modify_notFound_fails() {
+        final UserUpdateDto request = UserUpdateDto.builder()
+                .firstname(USER_2_FIRSTNAME)
+                .lastname(USER_2_LASTNAME)
+                .affiliation(USER_2_AFFILIATION)
+                .orcid(USER_2_ORCID)
+                .build();
+
+        /* test */
+        assertThrows(UserNotFoundException.class, () -> {
+            userService.modify(USER_2_ID, request);
+        });
+    }
+
+    @Test
+    public void updatePassword_succeeds() throws UserNotFoundException {
+        final UserPasswordDto request = UserPasswordDto.builder()
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        final User response = userService.updatePassword(USER_1_ID, request);
+        assertEquals(1, response.getCredentials().size());
+    }
+
+    @Test
+    public void updatePassword_notFound_fails() {
+        final UserPasswordDto request = UserPasswordDto.builder()
+                .password(USER_1_PASSWORD)
+                .build();
+
+        /* test */
+        assertThrows(UserNotFoundException.class, () -> {
+            userService.updatePassword(USER_2_ID, request);
+        });
+    }
+
+    @Test
+    @Transactional
+    public void toggleTheme_succeeds() throws UserNotFoundException, UserAttributeNotFoundException {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(true)
+                .build();
+
+        /* test */
+        final User response = userService.toggleTheme(USER_1_ID, request);
+        assertNotNull(response.getAttributes());
+        assertEquals(3, response.getAttributes().size());
+    }
+
+    @Test
+    public void toggleTheme_fails() {
+        final UserThemeSetDto request = UserThemeSetDto.builder()
+                .themeDark(true)
+                .build();
+
+        /* test */
+        assertThrows(UserNotFoundException.class, () -> {
+            userService.toggleTheme(USER_2_ID, request);
+        });
+    }
+
+    @Test
+    public void find_succeeds() throws UserNotFoundException {
+
+        /* test */
+        final User user = userService.find(USER_1_ID);
+        assertEquals(USER_1_ID, user.getId());
+    }
+
+    @Test
+    public void find_notFound_fails() {
+
+        /* test */
+        assertThrows(UserNotFoundException.class, () -> {
+            userService.find(USER_2_ID);
+        });
+    }
+
+    @Test
+    public void validateUsernameNotExists_succeeds() throws UserAlreadyExistsException {
+
+        /* test */
+        userService.validateUsernameNotExists(USER_2_USERNAME);
+    }
+
+    @Test
+    public void validateUsernameNotExists_fails() {
+
+        /* test */
+        assertThrows(UserAlreadyExistsException.class, () -> {
+            userService.validateUsernameNotExists(USER_1_USERNAME);
+        });
+    }
+
+    @Test
+    public void validateEmailNotExists_succeeds() throws UserEmailAlreadyExistsException {
+
+        /* test */
+        userService.validateEmailNotExists(USER_2_EMAIL);
+    }
+
+    @Test
+    public void validateEmailNotExists_fails() {
+
+        /* test */
+        assertThrows(UserEmailAlreadyExistsException.class, () -> {
+            userService.validateEmailNotExists(USER_1_EMAIL);
+        });
+    }
+}
diff --git a/dbrepo-user-service/rest-service/src/test/resources/application.properties b/dbrepo-user-service/rest-service/src/test/resources/application.properties
index ddba279a3bfcf1940585c1d6cb4c4608cd3f54c9..a2cfb02bead54bf217dd0152bb3170dda5e113f1 100644
--- a/dbrepo-user-service/rest-service/src/test/resources/application.properties
+++ b/dbrepo-user-service/rest-service/src/test/resources/application.properties
@@ -20,6 +20,8 @@ spring.jpa.show-sql=false
 # additional logging
 logging.level.root=error
 logging.level.at.tuwien.=trace
+logging.level.org.hibernate.SQL=debug
+logging.level.org.hibernate.type=trace
 
 # search service
 fda.consumers=2
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java
deleted file mode 100644
index 6af0750d6f5089a8442a7159eac2076462df2825..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/AmqpException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_ACCEPTABLE)
-public class AmqpException extends Exception {
-
-    public AmqpException(String msg) {
-        super(msg);
-    }
-
-    public AmqpException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public AmqpException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java
deleted file mode 100644
index c0c1e109de740d33646d7b91bb1a68ebabdc7616..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ColumnParseException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.EXPECTATION_FAILED)
-public class ColumnParseException extends Exception {
-
-    public ColumnParseException(String msg) {
-        super(msg);
-    }
-
-    public ColumnParseException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public ColumnParseException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java
deleted file mode 100644
index 85d49d4cb34b20b15fdc1441c69cbab0fe0a34f3..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ContainerNotFoundException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND)
-public class ContainerNotFoundException extends Exception {
-
-    public ContainerNotFoundException(String msg) {
-        super(msg);
-    }
-
-    public ContainerNotFoundException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public ContainerNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java
deleted file mode 100644
index 3c1f797647d67b4e1cb8148a4aba11b7d06aefc0..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseConnectionException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-import java.io.IOException;
-
-@ResponseStatus(code = HttpStatus.METHOD_NOT_ALLOWED)
-public class DatabaseConnectionException extends Exception {
-
-    public DatabaseConnectionException(String msg) {
-        super(msg);
-    }
-
-    public DatabaseConnectionException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public DatabaseConnectionException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java
deleted file mode 100644
index b9ca79c783048e8d0298db273abdb9462efeeec8..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/DatabaseNotFoundException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-import java.io.IOException;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND)
-public class DatabaseNotFoundException extends Exception {
-
-    public DatabaseNotFoundException(String msg) {
-        super(msg);
-    }
-
-    public DatabaseNotFoundException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public DatabaseNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java
deleted file mode 100644
index ab068e4245526e77b611d1c8571df867d5fc2cb6..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/FileStorageException.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.BAD_REQUEST)
-public class FileStorageException extends Exception {
-
-    public FileStorageException(String msg) {
-        super(msg);
-    }
-
-    public FileStorageException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public FileStorageException(Throwable thr) {
-        super(thr);
-    }
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java
deleted file mode 100644
index f0bb71f36492511efbe2c8c959dcdb97c679702f..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/IdentifierNotFoundException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND)
-public class IdentifierNotFoundException extends Exception {
-
-    public IdentifierNotFoundException(String msg) {
-        super(msg);
-    }
-
-    public IdentifierNotFoundException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public IdentifierNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java
deleted file mode 100644
index 70963128f8410f856029916c606efe152957f12a..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ImageNotSupportedException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-import java.io.IOException;
-
-@ResponseStatus(code = HttpStatus.CONFLICT)
-public class ImageNotSupportedException extends Exception {
-
-    public ImageNotSupportedException(String msg) {
-        super(msg);
-    }
-
-    public ImageNotSupportedException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public ImageNotSupportedException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java
deleted file mode 100644
index 9d56aec9c2752c2c37e7b31f227950ad94c95ef3..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/PaginationException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.BAD_REQUEST)
-public class PaginationException extends Exception {
-
-    public PaginationException(String msg) {
-        super(msg);
-    }
-
-    public PaginationException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public PaginationException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java
deleted file mode 100644
index 48d3bb0ad9ec2fb1f7c6b9727f2a3e700291cbc6..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryAlreadyPersistedException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.CONFLICT)
-public class QueryAlreadyPersistedException extends Exception {
-
-    public QueryAlreadyPersistedException(String msg) {
-        super(msg);
-    }
-
-    public QueryAlreadyPersistedException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public QueryAlreadyPersistedException(Throwable thr) { super(thr);
-    }
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java
deleted file mode 100644
index 3d81b6ba4e659b49b59591fa8e9baec31767615a..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryMalformedException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-import java.io.IOException;
-
-@ResponseStatus(code = HttpStatus.BAD_REQUEST)
-public class QueryMalformedException extends Exception {
-
-    public QueryMalformedException(String msg) {
-        super(msg);
-    }
-
-    public QueryMalformedException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public QueryMalformedException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java
deleted file mode 100644
index a5e90754898f19f6cce8938d2385f3f9fecd43e4..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryNotFoundException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND)
-public class QueryNotFoundException extends Exception {
-
-    public QueryNotFoundException(String msg) {
-        super(msg);
-    }
-
-    public QueryNotFoundException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public QueryNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java
deleted file mode 100644
index b1f472f2a1179200475b7952dd459af0ee7f7b6a..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.GATEWAY_TIMEOUT)
-public class QueryStoreException  extends Exception {
-
-    public QueryStoreException(String msg) {
-        super(msg);
-    }
-
-    public QueryStoreException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public QueryStoreException(Throwable thr) { super(thr);
-    }
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/SortException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/SortException.java
deleted file mode 100644
index 7415590ad637461baac4c9bf1d68b7d411054b19..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/SortException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.BAD_REQUEST)
-public class SortException extends Exception {
-
-    public SortException(String msg) {
-        super(msg);
-    }
-
-    public SortException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public SortException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java
deleted file mode 100644
index 542c789ad548084b1a98720630246514b431efcb..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableMalformedException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.LOCKED)
-public class TableMalformedException extends Exception {
-
-    public TableMalformedException(String msg) {
-        super(msg);
-    }
-
-    public TableMalformedException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public TableMalformedException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java
deleted file mode 100644
index 89fa3ed467e76998431c2b366bceb83804824f38..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TableNotFoundException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND)
-public class TableNotFoundException extends Exception {
-
-    public TableNotFoundException(String msg) {
-        super(msg);
-    }
-
-    public TableNotFoundException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public TableNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java
deleted file mode 100644
index 87e5e4a483f9dfe38f9563be9a9d275415b5a0d9..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/TupleDeleteException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.CONFLICT)
-public class TupleDeleteException extends Exception {
-
-    public TupleDeleteException(String msg) {
-        super(msg);
-    }
-
-    public TupleDeleteException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public TupleDeleteException(Throwable thr) { super(thr);
-    }
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/UserEmailAlreadyExistsException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/UserEmailAlreadyExistsException.java
new file mode 100644
index 0000000000000000000000000000000000000000..803e94aa0a8c6ec2cf8f1d699d2fa490e76e0593
--- /dev/null
+++ b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/UserEmailAlreadyExistsException.java
@@ -0,0 +1,21 @@
+package at.tuwien.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(code = HttpStatus.EXPECTATION_FAILED)
+public class UserEmailAlreadyExistsException extends Exception {
+
+    public UserEmailAlreadyExistsException(String message) {
+        super(message);
+    }
+
+    public UserEmailAlreadyExistsException(String message, Throwable thr) {
+        super(message, thr);
+    }
+
+    public UserEmailAlreadyExistsException(Throwable thr) {
+        super(thr);
+    }
+
+}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java
deleted file mode 100644
index 5dfdaf170eaccc76da2290673448be19511e4fab..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewMalformedException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.LOCKED)
-public class ViewMalformedException extends Exception {
-
-    public ViewMalformedException(String msg) {
-        super(msg);
-    }
-
-    public ViewMalformedException(String msg, Throwable thr) {
-        super(msg, thr);
-    }
-
-    public ViewMalformedException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java b/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java
deleted file mode 100644
index 2f260975ff4746858184a4c8e5d4ee8268425625..0000000000000000000000000000000000000000
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/exception/ViewNotFoundException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package at.tuwien.exception;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "View not found")
-public class ViewNotFoundException extends Exception {
-
-    public ViewNotFoundException(String message) {
-        super(message);
-    }
-
-    public ViewNotFoundException(String message, Throwable thr) {
-        super(message, thr);
-    }
-
-    public ViewNotFoundException(Throwable thr) {
-        super(thr);
-    }
-
-}
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/service/UserService.java b/dbrepo-user-service/services/src/main/java/at/tuwien/service/UserService.java
index c607aedaa7e24f26cd0b1bd136678f7db2ec5a36..0026eb377a762214937c80da8d12831476ab9130 100644
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/service/UserService.java
+++ b/dbrepo-user-service/services/src/main/java/at/tuwien/service/UserService.java
@@ -16,22 +16,77 @@ import java.util.UUID;
 public interface UserService {
 
     /**
-     * Finds all users
+     * Finds all users in the metadata database
      *
      * @return The list of users.
      */
     List<User> findAll();
 
-    User create(SignupRequestDto data, Realm realm, Role role) throws RemoteUnavailableException, UserNotFoundException,
-            UserAlreadyExistsException;
-
-    User modify(UUID id, UserUpdateDto data, Principal principal) throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException;
+    /**
+     * Creates a user in the metadata database managed by Keycloak in the given realm and with given role.
+     *
+     * @param data  The user data.
+     * @param realm The realm this user should be created.
+     * @param role  The role.
+     * @return The user, if successful. False otherwise.
+     * @throws UserAlreadyExistsException The user already exists in the metadata database.
+     */
+    User create(SignupRequestDto data, Realm realm, Role role) throws UserAlreadyExistsException;
 
-    User updatePassword(UUID id, UserPasswordDto data, Principal principal) throws UserNotFoundException,
-            ForeignUserException;
+    /**
+     * Updates the user information for a user with given id in the metadata database.
+     *
+     * @param id   The user id.
+     * @param data The user information.
+     * @return The user if successful. False otherwise.
+     * @throws UserNotFoundException          The user was not found.
+     * @throws UserAttributeNotFoundException One or more user attributes for the user information were not found.
+     */
+    User modify(UUID id, UserUpdateDto data) throws UserNotFoundException, UserAttributeNotFoundException;
 
-    User toggleTheme(UUID id, UserThemeSetDto data, Principal principal) throws UserNotFoundException, ForeignUserException, UserAttributeNotFoundException;
+    /**
+     * Updates the user password for a user with given id.
+     *
+     * @param id   The user id.
+     * @param data The new password.
+     * @return The user if successful. False otherwise.
+     * @throws UserNotFoundException The user was not found.
+     */
+    User updatePassword(UUID id, UserPasswordDto data) throws UserNotFoundException;
 
+    /**
+     * Updates the user theme for a user with given id.
+     *
+     * @param id   The user id.
+     * @param data The user theme.
+     * @return The user if successful. False otherwise.
+     * @throws UserNotFoundException          The user was not found.
+     * @throws UserAttributeNotFoundException One or more user attributes for the user information were not found.
+     */
+    User toggleTheme(UUID id, UserThemeSetDto data) throws UserNotFoundException, UserAttributeNotFoundException;
 
+    /**
+     * Finds a specific user in the metadata database by given id.
+     *
+     * @param id The user id.
+     * @return The user if successful. False otherwise.
+     * @throws UserNotFoundException The user was not found.
+     */
     User find(UUID id) throws UserNotFoundException;
+
+    /**
+     * Validates if a user with the given username already exists in the metadata database.
+     *
+     * @param username The username.
+     * @throws UserAlreadyExistsException The user with this username already exists.
+     */
+    void validateUsernameNotExists(String username) throws UserAlreadyExistsException;
+
+    /**
+     * Validates if a user with the given email already exists in the metadata database.
+     *
+     * @param email The email.
+     * @throws UserEmailAlreadyExistsException The user with this email already exists.
+     */
+    void validateEmailNotExists(String email) throws UserEmailAlreadyExistsException;
 }
diff --git a/dbrepo-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java b/dbrepo-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java
index 56f67ca22d47699a3c6a2b75e82a06972bc7ea20..0d0d80b5b83e23ed12a251a7775ca17dac4e469b 100644
--- a/dbrepo-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java
+++ b/dbrepo-user-service/services/src/main/java/at/tuwien/service/impl/UserServiceImpl.java
@@ -22,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.PBEKeySpec;
 import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
 import java.security.SecureRandom;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
@@ -60,24 +59,14 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
+    @Transactional(readOnly = true)
     public List<User> findAll() {
         return userRepository.findAll();
     }
 
     @Override
-    public User create(SignupRequestDto data, Realm realm, Role role) throws RemoteUnavailableException, UserNotFoundException,
-            UserAlreadyExistsException {
-        /* check */
-        final Optional<User> optional = userRepository.findByUsername(data.getUsername());
-        if (optional.isPresent()) {
-            log.error("User with username {} already exists", data.getUsername());
-            throw new UserAlreadyExistsException("User with username " + data.getUsername() + " already exists");
-        }
-        final Optional<User> optional2 = userRepository.findByEmail(data.getEmail());
-        if (optional2.isPresent()) {
-            log.error("User with email {} already exists", data.getUsername());
-            throw new UserAlreadyExistsException("User with email " + data.getUsername() + " already exists");
-        }
+    @Transactional(rollbackFor = RuntimeException.class)
+    public User create(SignupRequestDto data, Realm realm, Role role) throws UserAlreadyExistsException {
         /* create secret */
         final byte[] salt = getSalt();
         final StringBuilder secretData = new StringBuilder("{\"value\":\"")
@@ -111,30 +100,21 @@ public class UserServiceImpl implements UserService {
         credential = credentialRepository.save(credential);
         user.setCredentials(List.of(credential));
         user.setAttributes(List.of(userAttribute1, userAttribute2, userAttribute3));
-        final RoleMapping tmp2 = RoleMapping.builder()
-                .userId(user.getId())
-                .roleId(role.getId())
-                .build();
-        roleMappingRepository.save(tmp2);
         user.setRoles(List.of(role));
         log.info("Created user with id {}", user.getId());
-        log.debug("created user {}", user);
         return user;
     }
 
     @Override
-    public User modify(UUID id, UserUpdateDto data, Principal principal) throws UserNotFoundException,
-            ForeignUserException, UserAttributeNotFoundException {
+    @Transactional
+    public User modify(UUID id, UserUpdateDto data) throws UserNotFoundException,
+            UserAttributeNotFoundException {
         /* check */
-        User user = find(id);
-        if (!user.equalsPrincipal(principal)) {
-            log.error("Failed to modify user: attempting to modify other user");
-            throw new ForeignUserException("Failed to modify user: attempting to modify other user");
-        }
-        user.setFirstname(data.getFirstname());
-        user.setLastname(data.getLastname());
+        final User entity = find(id);
+        entity.setFirstname(data.getFirstname());
+        entity.setLastname(data.getLastname());
         /* save in metadata database */
-        user = userRepository.save(user);
+        final User user = userRepository.save(entity);
         log.info("Modified user with id {}", user.getId());
         /* modify attributes */
         userAttributeService.update(user.getId(), "orcid", data.getOrcid());
@@ -143,14 +123,9 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
-    public User updatePassword(UUID id, UserPasswordDto data, Principal principal) throws UserNotFoundException,
-            ForeignUserException {
-        /* check */
+    @Transactional(rollbackFor = RuntimeException.class)
+    public User updatePassword(UUID id, UserPasswordDto data) throws UserNotFoundException {
         final User user = find(id);
-        if (!user.equalsPrincipal(principal)) {
-            log.error("Failed to modify user: attempting to modify other user");
-            throw new ForeignUserException("Failed to modify user: attempting to modify other user");
-        }
         /* create secret */
         final byte[] salt = getSalt();
         final StringBuilder secretData = new StringBuilder("{\"value\":\"")
@@ -158,7 +133,9 @@ public class UserServiceImpl implements UserService {
                 .append("\",\"salt\":\"")
                 .append(Base64.encodeBytes(salt))
                 .append("\",\"additionalParameters\":{}}");
-        Credential credential = Credential.builder()
+        final Credential entity = Credential.builder()
+                .id(UUID.randomUUID())
+                .userId(user.getId())
                 .createdDate(Instant.now().toEpochMilli())
                 .secretData(secretData.toString())
                 .type("password")
@@ -166,14 +143,15 @@ public class UserServiceImpl implements UserService {
                 .credentialData("{\"hashIterations\":" + DEFAULT_ITERATIONS + ",\"algorithm\":\"" + ID + "\",\"additionalParameters\":{}}")
                 .build();
         /* save */
-        credential = credentialRepository.save(credential);
+        final Credential credential = credentialRepository.save(entity);
         user.setCredentials(List.of(credential));
         log.info("Updated user password with id {}", user.getId());
         return user;
     }
 
     @Override
-    public User toggleTheme(UUID id, UserThemeSetDto data, Principal principal) throws UserNotFoundException,
+    @Transactional
+    public User toggleTheme(UUID id, UserThemeSetDto data) throws UserNotFoundException,
             UserAttributeNotFoundException {
         /* check */
         final User user = find(id);
@@ -183,6 +161,7 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
+    @Transactional(readOnly = true)
     public User find(UUID id) throws UserNotFoundException {
         final Optional<User> optional = userRepository.findById(id);
         if (optional.isEmpty()) {
@@ -192,6 +171,26 @@ public class UserServiceImpl implements UserService {
         return optional.get();
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    public void validateUsernameNotExists(String username) throws UserAlreadyExistsException {
+        final Optional<User> optional = userRepository.findByUsername(username);
+        if (optional.isPresent()) {
+            log.error("User with username {} already exists", username);
+            throw new UserAlreadyExistsException("User with username " + username + " already exists");
+        }
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public void validateEmailNotExists(String email) throws UserEmailAlreadyExistsException {
+        final Optional<User> optional = userRepository.findByEmail(email);
+        if (optional.isPresent()) {
+            log.error("User with email {} already exists", email);
+            throw new UserEmailAlreadyExistsException("User with email already exists");
+        }
+    }
+
     private String encodedCredential(String rawPassword, int iterations, byte[] salt, int derivedKeySize) {
         final String rawPasswordWithPadding = PaddingUtils.padding(rawPassword, MAX_PADDING_LENGTH);
         final KeySpec spec = new PBEKeySpec(rawPasswordWithPadding.toCharArray(), salt, iterations, derivedKeySize);