diff --git a/.gitignore b/.gitignore
index 06c8c148f1e9f45493f574a11c6789398246defd..ab11f8441c1690ede967897c0e64745f9ad30f86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ log/
 OUTBOX
 ConsistencyTest.xml
 testlog/
+authtoken/
 
 # python
 __pycache__
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8587b4072e1d7485406cd432aff9034e6c2e6b84..c1afaf3f2a5274ff9e9a3083dbfd788e629709c0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,7 +60,6 @@ test:
     - make easy-units
     - mvn antlr4:antlr4
     - mvn compile
-    - echo "defaultRealm = CaosDB" > conf/ext/usersources.ini
     - mvn test
 
 # Deploy: Trigger building of server image and integration tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88ac0b5297a401ebcc3edda414cce13d3142d3fd..71e19edb1f1069328a768ca05b9d487388f3c4e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- Support for deeply nested selectors in SELECT queries.
+- One-time Authentication Tokens for login without credentials and login with
+  particular permissions and roles for the course of the session.
 - `Entity/names` resource for retrieving all known entity names.
 - Scripting is simplified by adding a `home` directory, of which a copy is
   created for each called script and set as the `HOME` environment variable.
@@ -19,6 +22,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   Script for moving files (change their path) in the internal file system based
   on a two-column tsv file (with columns "from" and "to"). See
   [README.md](misc/move_files/README.md).
+- LDAP server may now be given and may be different from LDAP domain. See
+  `misc/pam_authentication/ldap.conf`
+- #47 - Sub-properties can now be queried, such as in
+  `SELECT window.width FROM house`.
+- Added support for versioning, if it is enabled on the backend.
+
 
 ### Changed
 
@@ -28,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Deprecated
 
-- 
+- CaosDBTerminal
 
 ### Removed
 
@@ -46,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   returned without ID but with a notice now.
 - #11 - pam_authentication leaks the password to unprivileged processes on the
   same machine.
+- #39 - quotes around datetimes in queries
+- #99 - Checksum updating resulted in infinite loop on server.
 
 ### Security (in case of vulnerabilities)
 
diff --git a/README_CONFIGURATION.md b/README_CONFIGURATION.md
index 55b47175762f15266e66bcca8d3b17143baaf578..27045eea4523c5009e246fe506ec6319965d8e9f 100644
--- a/README_CONFIGURATION.md
+++ b/README_CONFIGURATION.md
@@ -13,3 +13,9 @@ The default configuration can be overriden by
 
 in this order.
 
+# One-time Authentication Tokens
+
+One-time Authentication Tokens can be configure to be issued for special purposes (e.g. a call of a server-side script) or to be written to a file on a regular basis.
+
+An example of a configuration is located at `./conf/core/authtoken.example.yaml`.
+
diff --git a/README_SETUP.md b/README_SETUP.md
index 19a62bda4144550d18f2cd40ea43bbc8431ca903..554cd5dacbdffb637227e6be2b18ed1dad7877dc 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -12,6 +12,7 @@
 * libpam (if PAM authentication is required)
 * unzip
 * openpyxl (for XLS/ODS export)
+* openssl (if a custom TLS certificate is required)
 
 ### Install the requirements on Debian
 On Debian, the required packages can be installed with:
diff --git a/caosdb-webui b/caosdb-webui
index 136582641fb1b675d9630b4eacea54fbf7765eea..66026626089e2b514538510a1a6744868f46b661 160000
--- a/caosdb-webui
+++ b/caosdb-webui
@@ -1 +1 @@
-Subproject commit 136582641fb1b675d9630b4eacea54fbf7765eea
+Subproject commit 66026626089e2b514538510a1a6744868f46b661
diff --git a/conf/core/authtoken.example.yaml b/conf/core/authtoken.example.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..453630a852642db19cfc824bf660b004e26af5b2
--- /dev/null
+++ b/conf/core/authtoken.example.yaml
@@ -0,0 +1,94 @@
+# OneTimeAuthenticationToken Config
+#
+# One-Time Authentication (OTA) Tokens are a means to authenticate a client
+# without a password.
+#
+# This example config file illustrates several use cases of One-Time
+# Authentication Tokens.
+#
+# This yaml file contains an array of configuration objects which may have the
+# properties that are defined by the caosdb.server.accessControl.Config class.
+#
+# These properties are:
+#
+# - expiresAfter:: An integer timespan in milliseconds after the OTA Token was
+#     generated by the server when the token expires. So the token will not be
+#     valid after <creationDate> + <expiresAfter>.
+# - expiresAfterSeconds:: A convenient option which has the same meaning as
+#     expiresAfter but measured in seconds instead of milliseconds.
+# - roles:: A list of strings which are the user roles that the client has when
+#     it authenticates with this token. This is a way to give a client a set of
+#     roles which it wouldn't have otherwise. Consequently, the client also has
+#     all permissions of its roles.
+# - permissions:: A list of string which are the permissions that the client has
+#     when it authenticates with this token. This is another way to give a
+#     client permissions which it wouldn't have otherwise.
+# - maxReplays:: Normally a One-Time token can be used exactly once and
+#     invalidates the moment when the server recognizes that a client uses it.
+#     However, when a network connection is unreliable, it should be possible to
+#     attempt another login with the same token for a few times. And in another
+#     use case, it might be practical to authenticate several clients at the
+#     same time with the same authentication token. The maxReplays value is the
+#     number of times that a server accepts an OTA token as valid. The default
+#     is, of course, 1.
+# - replayTimeout:: An integer timespan in milliseconds. Because replays are a
+#     possible attack vector, the time span in which the same token may be used
+#     is limited by this value. The default value is configured by the server
+#     property 'ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS' which has a default of 30000,
+#     which is 30 seconds.
+# - replayTimeoutSeconds:: A convenient option which has the same meaning as
+#     replayTimeout but measured in seconds instead of milliseconds.
+# - output:: Defines how the OTA token is output by the server. If this property
+#     is not present the OTA is not output in any way but only used internally
+#     (see purpose). The 'output' object has the following properties:
+#      - file:: An absolute or relative path in the server's file system. This
+#        property means that the OTA is written to that file on server start.
+#      - schedule:: A string formatted according to the Quartz[1] library
+#        indicating when the OTA is renewed and the file is being overridden
+#        with the new OTA.
+# - purpose:: A string which is used (only internally so far) to generate an OTA
+#     for a special purpose, e.g. the execution of a server-side script with
+#     particular permissions. This way, an otherwise unprivileged client may
+#     execute a server-side script with all necessary permissions, if the client
+#     has the "SCRIPTING:EXECUTE:<script-path>" permission and this config file
+#     has a configuration with "purpose: SCRIPTING:EXECUTE:<script-path>"
+#     (case-sensitive).
+#
+# [1] http://www.quartz-scheduler.org/api/2.3.0/org/quartz/CronExpression.html
+#
+#
+# Examples:
+#
+# 1. Every client with the SCRIPTING:EXECUTE:administration/diagnostics.py
+#    permission can execute the administration/diagnostics.py script with the
+#    administration role (and not the client's roles).
+- purpose: SCRIPTING:EXECUTE:administration/diagnostics.py
+  roles:
+    - administration
+# 2. The server writes an OTA token with the administration role to
+#    "authtoken/admin_token_crud.txt" and refreshes that token every 10 seconds.
+- roles:
+    - administration
+  output:
+    file: "authtoken/admin_token_crud.txt"
+    schedule: "0/10 * * ? * * *"
+
+# 3. The server writes an OTA token with the administration role to
+#    "authtoken/admin_token_3_attempts.txt" which can be replayed 3 times in 10
+#    seconds. The OTA token is refreshed every 10 seconds.
+- roles:
+    - administration
+  output:
+    file: "authtoken/admin_token_3_attempts.txt"
+    schedule: "0/10 * * ? * * *"
+  maxReplays: 3
+  replayTimeout: 10000
+
+# 4. The server writes an OTA token with the administration role to
+#    "authtoken/admin_token_expired.txt" which expires immediately. Of course
+#    this is only useful for testing.
+- roles:
+    - administration
+  output:
+    file: "authtoken/admin_token_expired.txt"
+  expiresAfterSeconds: 0
diff --git a/conf/core/cache.ccf b/conf/core/cache.ccf
index 821e5d7862efb21e0aa13f8410886c6c14b10a7c..b4e1f93596a6170ef04ec373dc7a8d70e51fedcc 100644
--- a/conf/core/cache.ccf
+++ b/conf/core/cache.ccf
@@ -28,6 +28,8 @@ jcs.region.BACKEND_JobRules.cacheattributes.MaxObjects=103
 jcs.region.BACKEND_SparseEntities
 jcs.region.BACKEND_SparseEntities.cacheattributes.MaxObjects=1002
 
+jcs.region.BACKEND_RetrieveFullVersionInfo
+jcs.region.BACKEND_RetrieveFullVersionInfo.cacheattributes.MaxObjects=1006
 
 # PAM UserSource Caching: Cached Items expire after 60 seconds if they are not requested (idle) and after 600 seconds max.
 # PAM_UnixUserGroups
diff --git a/conf/core/server.conf b/conf/core/server.conf
index 358f6b5c14106b87d7a676e2262474772ae98512..3507886b0475341b06a8d676845e9280c67b991b 100644
--- a/conf/core/server.conf
+++ b/conf/core/server.conf
@@ -67,7 +67,7 @@ MYSQL_USER_NAME=caosdb
 # Password for the user
 MYSQL_USER_PASSWORD=caosdb
 # Schema of mysql procedures and tables which is required by this CaosDB instance
-MYSQL_SCHEMA_VERSION=v3.0.0-rc1
+MYSQL_SCHEMA_VERSION=v3.0.0-rc2
 
 
 # --------------------------------------------------
@@ -114,10 +114,21 @@ CERTIFICATES_KEY_STORE_PASSWORD=
 # 10 min
 SESSION_TIMEOUT_MS=600000
 
-# Time after which activation tokens for the activation of new users (internal
-# user sources) expire.
+# Time after which one-time tokens expire.
+# This is only a default value. The actual timeout of tokens can be
+# configured otherwise, for example in authtoken.yml.
 # 7days
-ACTIVATION_TIMEOUT_MS=604800000
+ONE_TIME_TOKEN_EXPIRES_MS=604800000
+
+# Path to config file for one time tokens, for example authtoken.yml.
+AUTHTOKEN_CONFIG=
+
+# Timeout after which a one-time token expires once it has been first consumed,
+# regardless of the maximum of replays that are allowed for that token. This is
+# only a default value. The actual timeout of tokens can be configured
+# otherwise.
+# 30 s
+ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS=30000
 
 # The value for the HTTP cache directive "max-age"
 WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800
@@ -170,3 +181,4 @@ CHECK_ENTITY_ACL_ROLES_MODE=MUST
 # part of any Entity ACL.
 GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml
 
+ENTITY_VERSIONING_ENABLED=true
diff --git a/makefile b/makefile
index 55631222cbb0613e6ccd634a609b594a50ff0951..e900742bededb1651c9e64963efe6db01014903e 100644
--- a/makefile
+++ b/makefile
@@ -47,6 +47,7 @@ run-single:
 
 formatting:
 	mvn fmt:format
+	autopep8 -ari scripting/
 
 # Compile into a standalone jar file
 jar: easy-units
diff --git a/misc/pam_authentication/ldap.conf b/misc/pam_authentication/ldap.conf
index 117d1074915e3cacd9a97b83b5a2e83e3c50d451..664dd7c97524242fdb1ea7015bbc0e26c087b062 100644
--- a/misc/pam_authentication/ldap.conf
+++ b/misc/pam_authentication/ldap.conf
@@ -1,7 +1,8 @@
 # This file is sourced by the LDAP authentication script
 
-
-# Set the ldap server here.  This is also used to generate a fully qualified
-# user name: <USER>@$LDAP_SERVER
-
+# Set the ldap server here.
 # LDAP_SERVER="example.com"
+
+# Set the ldap domain here. This is used to generate a fully qualified
+# user name: <USER>@$LDAP_DOMAIN
+# LDAP_DOMAIN="example.com"
diff --git a/misc/pam_authentication/ldap_authentication.sh b/misc/pam_authentication/ldap_authentication.sh
index f887bf99f47c827fd712d2189a5ca89ec2981e6c..1b86b8e1783399e2c43b92981a43789accb21e7d 100755
--- a/misc/pam_authentication/ldap_authentication.sh
+++ b/misc/pam_authentication/ldap_authentication.sh
@@ -35,7 +35,7 @@ exe_dir=$(dirname $0)
 
 # If the second argument is empty or "-", take password from stdin, else use the argument as a file.
 testpw() {
-    username="${1}@${LDAP_SERVER}"
+    username="${1}@${LDAP_DOMAIN}"
     pwfile="$2"
     pwargs=("-w" "$pwfile")
     if [[ $pwfile == "-" ]] ; then
diff --git a/pom.xml b/pom.xml
index c8f79b3dcc43759cc941244283d13b248d811bb8..4e71f97d2b8d89206e148adba67d259b24ade2a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,13 +35,13 @@
   <repositories>
     <repository>
       <id>maven-central</id>
-      <url>http://central.maven.org/maven2/</url>
+      <url>https://repo1.maven.org/maven2/</url>
       <name>Maven Central</name>
     </repository>
     <repository>
       <id>maven-restlet</id>
       <name>Public online Restlet repository</name>
-      <url>http://maven.restlet.com</url>
+      <url>https://maven.restlet.com</url>
     </repository>
     <repository>
       <id>local-maven-repo</id>
@@ -54,10 +54,20 @@
       <artifactId>easy-units</artifactId>
       <version>0.0.1-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.quartz-scheduler</groupId>
+      <artifactId>quartz</artifactId>
+      <version>2.3.2</version> 
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-yaml</artifactId>
+      <version>2.11.0</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.shiro</groupId>
       <artifactId>shiro-core</artifactId>
-      <version>1.4.1</version>
+      <version>1.5.3</version>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
@@ -90,7 +100,7 @@
     <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
-      <version>6.0.6</version>
+       <version>8.0.19</version>
     </dependency>
     <dependency>
       <groupId>org.xerial</groupId>
@@ -140,7 +150,7 @@
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-jcs-core</artifactId>
-      <version>2.1</version>
+      <version>2.2.1</version>
     </dependency>
     <dependency>
       <groupId>org.kohsuke</groupId>
diff --git a/scripting/bin/administration/diagnostics.py b/scripting/bin/administration/diagnostics.py
new file mode 100755
index 0000000000000000000000000000000000000000..ccecbd583ffdaf5f941a2df873ac10982560af31
--- /dev/null
+++ b/scripting/bin/administration/diagnostics.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2018 Research Group Biomedical Physics,
+# Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# ** end header
+#
+"""diagnostics.py
+
+A script which returns a json representation of various parameters which might
+be interesting for debugging the server-side scripting functionality and which
+should not be executable for non-admin users.
+"""
+
+import sys
+
+TEST_MODULES = [
+    "caosdb",
+    "numpy",
+    "pandas",
+    "validate_email"
+]
+
+
+def get_files():
+    from os import walk
+    from os.path import join
+    result = []
+    for p, dirs, files in walk("."):
+        for f in files:
+            result.append(join(p, f))
+        for d in dirs:
+            result.append(join(p, d))
+    return result
+
+
+def get_option(name, default=None):
+    for arg in sys.argv:
+        if arg.startswith("--{}=".format(name)):
+            index = len(name) + 3
+            return arg[index:]
+    return default
+
+
+def get_exit_code():
+    return int(get_option("exit", 0))
+
+
+def get_auth_token():
+    return get_option("auth-token")
+
+
+def get_query():
+    return get_option("query")
+
+
+def get_caosdb_info(auth_token):
+    import caosdb as db
+    result = dict()
+    result["version"] = db.version.version
+
+    try:
+        db.configure_connection(
+            auth_token=auth_token,
+            password_method="auth_token")
+
+        info = db.Info()
+
+        result["info"] = str(info)
+        result["username"] = info.user_info.name
+        result["realm"] = info.user_info.realm
+        result["roles"] = info.user_info.roles
+
+        # execute a query and return the results
+        query = get_query()
+        if query is not None:
+            query_result = db.execute_query(query)
+            result["query"] = (query, str(query_result))
+
+    except Exception as e:
+        result["exception"] = str(e)
+    return result
+
+
+def test_imports(modules):
+    result = dict()
+    for m in modules:
+        try:
+            i = __import__(m)
+            if hasattr(i, "__version__") and i.__version__ is not None:
+                v = i.__version__
+            else:
+                v = "unknown version"
+            result[m] = (True, v)
+        except ImportError as e:
+            result[m] = (False, str(e))
+    return result
+
+
+def main():
+    try:
+        import json
+    except ImportError:
+        print('{{"python_version":"{v}",'
+              '"python_path":["{p}"]}}'.format(v=sys.version,
+                                               p='","'.join(sys.path)))
+        raise
+
+    try:
+        diagnostics = dict()
+        diagnostics["python_version"] = sys.version
+        diagnostics["python_path"] = sys.path
+        diagnostics["call"] = sys.argv
+        diagnostics["import"] = test_imports(TEST_MODULES)
+        diagnostics["files"] = get_files()
+
+        auth_token = get_auth_token()
+        diagnostics["auth_token"] = auth_token
+
+        if diagnostics["import"]["caosdb"][0] is True:
+            diagnostics["caosdb"] = get_caosdb_info(auth_token)
+
+    finally:
+        json.dump(diagnostics, sys.stdout)
+
+    sys.exit(get_exit_code())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripting/bin/xls_from_csv.py b/scripting/bin/xls_from_csv.py
index 38dfbc1c9392ce60d338b102f9b9fd05309919d3..73bb6b0ac23fc2df093bd6797e864aad1f2d5592 100755
--- a/scripting/bin/xls_from_csv.py
+++ b/scripting/bin/xls_from_csv.py
@@ -93,7 +93,8 @@ def _parse_arguments():
     parser.add_argument('-t', '--tempdir', required=False, default=tempdir,
                         help="Temporary dir for saving the result.")
     parser.add_argument('-a', '--auth-token', required=False,
-                        help="An authentication token (not needed, only for compatibility).")
+                        help=("An authentication token (not needed, only for "
+                              "compatibility)."))
     parser.add_argument('tsv', help="The tsv file.")
     return parser.parse_args()
 
@@ -104,5 +105,6 @@ def main():
     filename = _write_xls(dataframe, directory=args.tempdir)
     print(filename)
 
+
 if __name__ == "__main__":
     main()
diff --git a/src/main/java/caosdb/datetime/UTCDateTime.java b/src/main/java/caosdb/datetime/UTCDateTime.java
index 215de67befe403aa6a202ed178244b3de898d23e..db66ef0ad08abf0d52a8f3b1b97c6a20cc613c8a 100644
--- a/src/main/java/caosdb/datetime/UTCDateTime.java
+++ b/src/main/java/caosdb/datetime/UTCDateTime.java
@@ -296,7 +296,7 @@ public class UTCDateTime implements Interval {
     throw new NullPointerException("toString method!!!");
   }
 
-  public static UTCDateTime UTCSeconds(final Long utcseconds, final Integer nanosecond) {
+  public static UTCDateTime UTCSeconds(final Long utcseconds, final Integer nanoseconds) {
     if (LEAP_SECONDS.isEmpty()) {
       initLeapSeconds();
     }
@@ -310,10 +310,10 @@ public class UTCDateTime implements Interval {
     if (leapSeconds2 != leapSeconds && LEAP_SECONDS.contains(systemSeconds)) {
       gc.add(Calendar.SECOND, -1);
       return new UTCDateTime(
-          systemSeconds, leapSeconds, nanosecond, new LeapSecondDateTimeStringStrategy(gc, 1));
+          systemSeconds, leapSeconds, nanoseconds, new LeapSecondDateTimeStringStrategy(gc, 1));
     } else {
       return new UTCDateTime(
-          systemSeconds, leapSeconds, nanosecond, new GregorianCalendarDateTimeStringStrategy(gc));
+          systemSeconds, leapSeconds, nanoseconds, new GregorianCalendarDateTimeStringStrategy(gc));
     }
   }
 
diff --git a/src/main/java/caosdb/server/CaosAuthenticator.java b/src/main/java/caosdb/server/CaosAuthenticator.java
index 25f112aa52050a0b872b2ff83e95400ad4bd925e..8fefee3b453dabc0c613228e2495acacb19deaa0 100644
--- a/src/main/java/caosdb/server/CaosAuthenticator.java
+++ b/src/main/java/caosdb/server/CaosAuthenticator.java
@@ -22,12 +22,13 @@
  */
 package caosdb.server;
 
+import caosdb.server.accessControl.AnonymousAuthenticationToken;
 import caosdb.server.accessControl.AuthenticationUtils;
-import caosdb.server.accessControl.OneTimeAuthenticationToken;
-import caosdb.server.accessControl.SessionToken;
 import caosdb.server.resource.DefaultResource;
+import caosdb.server.utils.ServerMessages;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.subject.Subject;
 import org.restlet.Context;
 import org.restlet.Request;
@@ -48,50 +49,26 @@ public class CaosAuthenticator extends Authenticator {
   protected boolean authenticate(final Request request, final Response response) {
     final Subject subject = SecurityUtils.getSubject();
 
-    return attemptOneTimeTokenLogin(subject, request) || attemptSessionValidation(subject, request);
+    return attemptSessionValidation(subject, request);
   }
 
   private static boolean attemptSessionValidation(final Subject subject, final Request request) {
     try {
-      final SessionToken sessionToken =
+      final AuthenticationToken sessionToken =
           AuthenticationUtils.parseSessionTokenCookie(
-              request.getCookies().getFirst(AuthenticationUtils.SESSION_TOKEN_COOKIE), null);
+              request.getCookies().getFirst(AuthenticationUtils.SESSION_TOKEN_COOKIE));
 
       if (sessionToken != null) {
         subject.login(sessionToken);
       }
-    } catch (AuthenticationException e) {
-      logger.info("LOGIN_FAILED", e);
-    }
-    // anonymous users
-    if (!subject.isAuthenticated()
-        && CaosDBServer.getServerProperty(ServerProperties.KEY_AUTH_OPTIONAL)
-            .equalsIgnoreCase("TRUE")) {
-      subject.login(AuthenticationUtils.ANONYMOUS_USER);
-    }
-    return subject.isAuthenticated();
-  }
 
-  private static boolean attemptOneTimeTokenLogin(final Subject subject, final Request request) {
-    try {
-      OneTimeAuthenticationToken oneTimeToken = null;
-
-      // try and parse from the query segment of the uri
-      oneTimeToken =
-          AuthenticationUtils.parseOneTimeTokenQuerySegment(
-              request.getResourceRef().getQueryAsForm().getFirstValue("AuthToken"), null);
-
-      // try and parse from cookie
-      if (oneTimeToken == null) {
-        oneTimeToken =
-            AuthenticationUtils.parseOneTimeTokenCookie(
-                request.getCookies().getFirst(AuthenticationUtils.ONE_TIME_TOKEN_COOKIE), null);
+      // anonymous users
+      if (!subject.isAuthenticated()
+          && CaosDBServer.getServerProperty(ServerProperties.KEY_AUTH_OPTIONAL)
+              .equalsIgnoreCase("TRUE")) {
+        subject.login(AnonymousAuthenticationToken.getInstance());
       }
-
-      if (oneTimeToken != null) {
-        subject.login(oneTimeToken);
-      }
-    } catch (final AuthenticationException e) {
+    } catch (AuthenticationException e) {
       logger.info("LOGIN_FAILED", e);
     }
     return subject.isAuthenticated();
@@ -100,7 +77,7 @@ public class CaosAuthenticator extends Authenticator {
   @Override
   protected int unauthenticated(final Request request, final Response response) {
     final DefaultResource defaultResource =
-        new DefaultResource(AuthenticationUtils.UNAUTHENTICATED.toElement());
+        new DefaultResource(ServerMessages.UNAUTHENTICATED.toElement());
     defaultResource.init(getContext(), request, response);
     defaultResource.handle();
     response.setStatus(org.restlet.data.Status.CLIENT_ERROR_UNAUTHORIZED);
diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java
index 34bfe3e2911955fa724ea5e6ac7258d80f9cc120..cdee262f08836071d19e5a75038f4291a917b1ff 100644
--- a/src/main/java/caosdb/server/CaosDBServer.java
+++ b/src/main/java/caosdb/server/CaosDBServer.java
@@ -19,12 +19,13 @@
  */
 package caosdb.server;
 
+import caosdb.server.accessControl.AnonymousAuthenticationToken;
 import caosdb.server.accessControl.AnonymousRealm;
 import caosdb.server.accessControl.AuthenticationUtils;
 import caosdb.server.accessControl.CaosDBAuthorizingRealm;
 import caosdb.server.accessControl.CaosDBDefaultRealm;
-import caosdb.server.accessControl.OneTimeTokenRealm;
-import caosdb.server.accessControl.Principal;
+import caosdb.server.accessControl.ConsumedInfoCleanupJob;
+import caosdb.server.accessControl.OneTimeAuthenticationToken;
 import caosdb.server.accessControl.SessionToken;
 import caosdb.server.accessControl.SessionTokenRealm;
 import caosdb.server.database.BackendTransaction;
@@ -63,7 +64,6 @@ import caosdb.server.transaction.ChecksumUpdater;
 import caosdb.server.utils.FileUtils;
 import caosdb.server.utils.Initialization;
 import caosdb.server.utils.NullPrintStream;
-import caosdb.server.utils.Utils;
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -74,17 +74,22 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Properties;
 import java.util.TimeZone;
+import java.util.UUID;
 import java.util.logging.Handler;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.config.Ini;
 import org.apache.shiro.config.Ini.Section;
-import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.env.BasicIniEnvironment;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
-import org.apache.shiro.util.Factory;
 import org.apache.shiro.util.ThreadContext;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.impl.StdSchedulerFactory;
 import org.restlet.Application;
 import org.restlet.Component;
 import org.restlet.Context;
@@ -118,11 +123,68 @@ public class CaosDBServer extends Application {
   private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>();
   private static boolean START_BACKEND = true;
   private static boolean INSECURE = false;
+  public static final String REQUEST_TIME_LOGGER = "REQUEST_TIME_LOGGER";
+  public static final String REQUEST_ERRORS_LOGGER = "REQUEST_ERRORS_LOGGER";
+  private static Scheduler SCHEDULER;
 
   public static String getServerProperty(final String key) {
     return getServerProperties().getProperty(key);
   }
 
+  /**
+   * This main method starts up a web application that will listen on a port defined in the config
+   * file.
+   *
+   * @param args One option temporarily (for testing) available: silent: If present: disable
+   *     System.out-stream (stream to a NullPrintStream). This makes the response of the database
+   *     amazingly faster.
+   * @throws IOException
+   * @throws FileNotFoundException
+   * @throws SecurityException
+   * @throws Exception If problems occur.
+   */
+  public static void main(final String[] args)
+      throws SecurityException, FileNotFoundException, IOException {
+    try {
+      init(args);
+      initScheduler();
+      initServerProperties();
+      initTimeZone();
+      initOneTimeTokens();
+      initShiro();
+      initBackend();
+      initWebServer();
+      initShutDownHook();
+    } catch (Exception e1) {
+      logger.error("Could not start the server.", e1);
+      System.exit(1);
+    }
+  }
+
+  private static void init(final String[] args) {
+    // Important change:
+    // Make silent the default option
+    START_GUI = false;
+    for (final String s : args) {
+      if (s.equals("silent")) {
+        START_GUI = false;
+      } else if (s.equals("gui")) {
+        START_GUI = true;
+      } else if (s.equals("nobackend")) {
+        START_BACKEND = false;
+      } else if (s.equals("insecure")) {
+        INSECURE = true;
+      }
+    }
+    INSECURE = INSECURE && isDebugMode(); // only allow insecure in debug mode
+    START_BACKEND = START_BACKEND || !isDebugMode(); // always start backend if not in debug mode
+  }
+
+  private static void initScheduler() throws SchedulerException {
+    SCHEDULER = StdSchedulerFactory.getDefaultScheduler();
+    SCHEDULER.start();
+  }
+
   public static void initServerProperties() throws IOException {
     SERVER_PROPERTIES = ServerProperties.initServerProperties();
   }
@@ -206,21 +268,15 @@ public class CaosDBServer extends Application {
     }
   }
 
-  private static void init(final String[] args) {
-    // Important change:
-    // Make silent the default option
-    START_GUI = false;
-    for (final String s : args) {
-      if (s.equals("silent")) {
-        START_GUI = false;
-      } else if (s.equals("gui")) {
-        START_GUI = true;
-      } else if (s.equals("nobackend")) {
-        START_BACKEND = false;
-      } else if (s.equals("insecure")) {
-        INSECURE = true;
-      }
-    }
+  public static void initOneTimeTokens() throws Exception {
+    OneTimeAuthenticationToken.initConfig();
+    ConsumedInfoCleanupJob.scheduleDaily();
+  }
+
+  public static void initShiro() {
+    // init Shiro (user authentication/authorization and session management)
+    final Ini config = getShiroConfig();
+    initShiro(config);
   }
 
   public static Ini getShiroConfig() {
@@ -228,12 +284,11 @@ public class CaosDBServer extends Application {
     final Section mainSec = config.addSection("main");
     mainSec.put("CaosDB", CaosDBDefaultRealm.class.getCanonicalName());
     mainSec.put("SessionTokenValidator", SessionTokenRealm.class.getCanonicalName());
-    mainSec.put("OneTimeTokenValidator", OneTimeTokenRealm.class.getCanonicalName());
     mainSec.put("CaosDBAuthorizingRealm", CaosDBAuthorizingRealm.class.getCanonicalName());
     mainSec.put("AnonymousRealm", AnonymousRealm.class.getCanonicalName());
     mainSec.put(
         "securityManager.realms",
-        "$CaosDB, $SessionTokenValidator, $OneTimeTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm");
+        "$CaosDB, $SessionTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm");
 
     // disable shiro's default session management. We have quasi-stateless
     // sessions
@@ -243,43 +298,15 @@ public class CaosDBServer extends Application {
     return config;
   }
 
-  /**
-   * This main method starts up a web application that will listen on a port defined in the config
-   * file.
-   *
-   * @param args One option temporarily (for testing) available: silent: If present: disable
-   *     System.out-stream (stream to a NullPrintStream). This makes the response of the database
-   *     amazingly faster.
-   * @throws IOException
-   * @throws FileNotFoundException
-   * @throws SecurityException
-   * @throws Exception If problems occur.
-   */
-  public static void main(final String[] args)
-      throws SecurityException, FileNotFoundException, IOException {
-    try {
-      init(args);
-      initServerProperties();
-      initTimeZone();
-    } catch (IOException | InterruptedException e1) {
-      logger.error("Could not configure the server.", e1);
-      System.exit(1);
-    }
-
-    INSECURE = INSECURE && isDebugMode(); // only allow insecure in debug mode
-    START_BACKEND = START_BACKEND || !isDebugMode(); // always start backend if
-    // not in debug mode
-
-    // init Shiro (user authentication/authorization and session management)
-    final Ini config = getShiroConfig();
-    final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config);
-    final SecurityManager securityManager = factory.getInstance();
+  public static void initShiro(Ini config) {
+    BasicIniEnvironment env = new BasicIniEnvironment(config);
+    final SecurityManager securityManager = env.getSecurityManager();
     SecurityUtils.setSecurityManager(securityManager);
+  }
 
-    final Initialization init = Initialization.setUp();
-    try {
-      // init backend
-      if (START_BACKEND) {
+  public static void initBackend() throws Exception {
+    if (START_BACKEND) {
+      try (final Initialization init = Initialization.setUp()) {
         BackendTransaction.init();
 
         // init benchmark
@@ -296,65 +323,60 @@ public class CaosDBServer extends Application {
 
         // ChecksumUpdater
         ChecksumUpdater.start();
-      } else {
-        logger.info("NO BACKEND");
       }
+    } else {
+      logger.info("NO BACKEND");
+    }
+  }
 
-      // GUI
-      if (START_GUI) {
-        final CaosDBTerminal caosDBTerminal = new CaosDBTerminal();
-        caosDBTerminal.setName("CaosDBTerminal");
-        caosDBTerminal.start();
+  private static void initWebServer() throws Exception {
+    final int port_https =
+        Integer.parseInt(getServerProperty(ServerProperties.KEY_SERVER_PORT_HTTPS));
+    final int port_http =
+        Integer.parseInt(getServerProperty(ServerProperties.KEY_SERVER_PORT_HTTP));
+    int port_redirect_https;
+    try {
+      port_redirect_https =
+          Integer.parseInt(getServerProperty(ServerProperties.KEY_REDIRECT_HTTP_TO_HTTPS_PORT));
+    } catch (NumberFormatException e) {
+      port_redirect_https = port_https;
+    }
+    final int initialConnections =
+        Integer.parseInt(getServerProperty(ServerProperties.KEY_INITIAL_CONNECTIONS));
+    final int maxTotalConnections =
+        Integer.parseInt(getServerProperty(ServerProperties.KEY_MAX_CONNECTIONS));
 
-        addPreShutdownHook(
-            new Runnable() {
+    if (INSECURE) {
+      runHTTPServer(port_http, initialConnections, maxTotalConnections);
+    } else {
+      runHTTPSServer(
+          port_https, port_http, port_redirect_https, initialConnections, maxTotalConnections);
+    }
+  }
 
-              @Override
-              public void run() {
-                caosDBTerminal.shutDown();
-                SystemErrPanel.close();
-              }
-            });
-        // wait until the terminal is initialized.
-        Thread.sleep(1000);
-
-        // add Benchmark
-        StatsPanel.addStat("TransactionBenchmark", TransactionBenchmark.getRootInstance());
-      } else {
-        logger.info("NO GUI");
-        System.setOut(new NullPrintStream());
-      }
+  public static void initGUI() throws InterruptedException {
+    if (START_GUI) {
+      final CaosDBTerminal caosDBTerminal = new CaosDBTerminal();
+      caosDBTerminal.setName("CaosDBTerminal");
+      caosDBTerminal.start();
 
-      // Web server properties
-      final int port_https =
-          Integer.parseInt(getServerProperty(ServerProperties.KEY_SERVER_PORT_HTTPS));
-      final int port_http =
-          Integer.parseInt(getServerProperty(ServerProperties.KEY_SERVER_PORT_HTTP));
-      int port_redirect_https;
-      try {
-        port_redirect_https =
-            Integer.parseInt(getServerProperty(ServerProperties.KEY_REDIRECT_HTTP_TO_HTTPS_PORT));
-      } catch (NumberFormatException e) {
-        port_redirect_https = port_https;
-      }
-      final int initialConnections =
-          Integer.parseInt(getServerProperty(ServerProperties.KEY_INITIAL_CONNECTIONS));
-      final int maxTotalConnections =
-          Integer.parseInt(getServerProperty(ServerProperties.KEY_MAX_CONNECTIONS));
-
-      init.release();
-
-      if (INSECURE) {
-        runHTTPServer(port_http, initialConnections, maxTotalConnections);
-      } else {
-        runHTTPSServer(
-            port_https, port_http, port_redirect_https, initialConnections, maxTotalConnections);
-      }
-      initShutDownHook();
-    } catch (final Exception e) {
-      logger.error("Server start failed.", e);
-      init.release();
-      System.exit(1);
+      addPreShutdownHook(
+          new Runnable() {
+
+            @Override
+            public void run() {
+              caosDBTerminal.shutDown();
+              SystemErrPanel.close();
+            }
+          });
+      // wait until the terminal is initialized.
+      Thread.sleep(1000);
+
+      // add Benchmark
+      StatsPanel.addStat("TransactionBenchmark", TransactionBenchmark.getRootInstance());
+    } else {
+      logger.info("NO GUI");
+      System.setOut(new NullPrintStream());
     }
   }
 
@@ -508,9 +530,6 @@ public class CaosDBServer extends Application {
     }
   }
 
-  public static final String REQUEST_TIME_LOGGER = "REQUEST_TIME_LOGGER";
-  public static final String REQUEST_ERRORS_LOGGER = "REQUEST_ERRORS_LOGGER";
-
   /**
    * Specify the dispatching restlet that maps URIs to their associated resources for processing.
    *
@@ -560,10 +579,10 @@ public class CaosDBServer extends Application {
           private void setSessionCookies(final Response response) {
 
             final Subject subject = SecurityUtils.getSubject();
+            // if authenticated as a normal user: generate and set session cookie.
             if (subject.isAuthenticated()
-                && subject.getPrincipal() != AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) {
-              final SessionToken sessionToken =
-                  SessionToken.generate((Principal) subject.getPrincipal(), null);
+                && subject.getPrincipal() != AnonymousAuthenticationToken.PRINCIPAL) {
+              final SessionToken sessionToken = SessionToken.generate(subject);
 
               // set session token cookie (httpOnly, secure cookie which
               // is used to recognize a user session)
@@ -823,6 +842,10 @@ public class CaosDBServer extends Application {
   public static Properties getServerProperties() {
     return SERVER_PROPERTIES;
   }
+
+  public static void scheduleJob(JobDetail job, Trigger trigger) throws SchedulerException {
+    SCHEDULER.scheduleJob(job, trigger);
+  }
 }
 
 class CaosDBComponent extends Component {
@@ -852,7 +875,7 @@ class CaosDBComponent extends Component {
   public void handle(final Request request, final Response response) {
     long t1 = System.currentTimeMillis();
     // The server request ID is just a long random number
-    request.getAttributes().put("SRID", Utils.getUID());
+    request.getAttributes().put("SRID", UUID.randomUUID().toString());
     response.setServerInfo(CaosDBServer.getServerInfo());
     super.handle(request, response);
     log(request, response, t1);
diff --git a/src/main/java/caosdb/server/ServerProperties.java b/src/main/java/caosdb/server/ServerProperties.java
index c1dd59f3902601ea90c021a4a7b723723d1b0670..29ba470ddbcad0e79db210917234562c4b58620a 100644
--- a/src/main/java/caosdb/server/ServerProperties.java
+++ b/src/main/java/caosdb/server/ServerProperties.java
@@ -88,7 +88,9 @@ public class ServerProperties extends Properties {
   public static final String KEY_BUGTRACKER_URI = "BUGTRACKER_URI";
 
   public static final String KEY_SESSION_TIMEOUT_MS = "SESSION_TIMEOUT_MS";
-  public static final String KEY_ACTIVATION_TIMEOUT_MS = "ACTIVATION_TIMEOUT_MS";
+  public static final String KEY_ONE_TIME_TOKEN_EXPIRES_MS = "ONE_TIME_TOKEN_EXPIRES_MS";
+  public static final String KEY_ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS =
+      "ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS";
 
   public static final String KEY_CACHE_CONF_LOC = "CACHE_CONF_LOC";
   public static final String KEY_CACHE_DISABLE = "CACHE_DISABLE";
@@ -129,6 +131,7 @@ public class ServerProperties extends Properties {
   public static final String KEY_TIMEZONE = "TIMEZONE";
   public static final String KEY_WEBUI_HTTP_HEADER_CACHE_MAX_AGE =
       "WEBUI_HTTP_HEADER_CACHE_MAX_AGE";
+  public static final String KEY_AUTHTOKEN_CONFIG = "AUTHTOKEN_CONFIG";
 
   /**
    * Read the config files and initialize the server properties.
diff --git a/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java
index cd3f86f61eb66759b3eb7d0c91c29dc23637000b..f3f62af2af319d342977a7fe2524b353f8e7adab 100644
--- a/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java
+++ b/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java
@@ -28,7 +28,7 @@ public class AnonymousAuthenticationToken implements AuthenticationToken {
 
   private static final long serialVersionUID = 1424325396819592888L;
   private static final AnonymousAuthenticationToken INSTANCE = new AnonymousAuthenticationToken();
-  public static final Object PRINCIPAL = new Object();
+  public static final Principal PRINCIPAL = new Principal("anonymous", "anonymous");
 
   private AnonymousAuthenticationToken() {}
 
@@ -37,7 +37,7 @@ public class AnonymousAuthenticationToken implements AuthenticationToken {
   }
 
   @Override
-  public Object getPrincipal() {
+  public Principal getPrincipal() {
     return PRINCIPAL;
   }
 
diff --git a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java
index 6e0fd5370ffcc2435067d68b3f2f810819ae9fbb..c3576031da2d9a598bd74a07fe12df62ed7de90d 100644
--- a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java
+++ b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -26,19 +28,15 @@ import static caosdb.server.utils.Utils.URLDecodeWithUTF8;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
-import caosdb.server.entity.Message;
-import caosdb.server.entity.Message.MessageType;
 import caosdb.server.permissions.ResponsibleAgent;
 import caosdb.server.permissions.Role;
 import caosdb.server.utils.Utils;
 import java.sql.Timestamp;
 import java.util.Collection;
 import java.util.LinkedList;
-import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
 import org.restlet.data.Cookie;
 import org.restlet.data.CookieSetting;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Useful static methods, mainly for parsing and serializing SessionTokens by the means of web
@@ -48,16 +46,16 @@ import org.slf4j.LoggerFactory;
  */
 public class AuthenticationUtils {
 
-  private static final Logger logger = LoggerFactory.getLogger(AuthenticationUtils.class);
-
-  public static final String ONE_TIME_TOKEN_COOKIE = "OneTimeToken";
-  public static final Message UNAUTHENTICATED =
-      new Message(MessageType.Error, 401, "Sign up, please!");
   public static final String SESSION_TOKEN_COOKIE = "SessionToken";
   public static final String SESSION_TIMEOUT_COOKIE = "SessionTimeOut";
 
-  public static final AuthenticationToken ANONYMOUS_USER =
-      AnonymousAuthenticationToken.getInstance();
+  public static boolean isAnonymous(Subject user) {
+    return AnonymousAuthenticationToken.PRINCIPAL.equals(user.getPrincipal());
+  }
+
+  public static boolean isAnonymous(Principal principal) {
+    return AnonymousAuthenticationToken.PRINCIPAL.equals(principal);
+  }
 
   /**
    * Create a cookie for a {@link SelfValidatingAuthenticationToken}. Returns null if the parameter
@@ -87,11 +85,8 @@ public class AuthenticationUtils {
     return null;
   }
 
-  public static CookieSetting createOneTimeTokenCookie(final OneTimeAuthenticationToken token) {
-    return createTokenCookie(AuthenticationUtils.ONE_TIME_TOKEN_COOKIE, token);
-  }
-
-  public static CookieSetting createSessionTokenCookie(final SessionToken token) {
+  public static CookieSetting createSessionTokenCookie(
+      final SelfValidatingAuthenticationToken token) {
     return createTokenCookie(AuthenticationUtils.SESSION_TOKEN_COOKIE, token);
   }
 
@@ -103,48 +98,16 @@ public class AuthenticationUtils {
    * @return A new SessionToken
    * @see {@link AuthenticationUtils#createSessionTokenCookie(SessionToken)}, {@link SessionToken}
    */
-  public static SessionToken parseSessionTokenCookie(final Cookie cookie, final String curry) {
+  public static SelfValidatingAuthenticationToken parseSessionTokenCookie(final Cookie cookie) {
     if (cookie != null) {
       final String tokenString = URLDecodeWithUTF8(cookie.getValue());
       if (tokenString != null && !tokenString.equals("")) {
-        try {
-          return SessionToken.parse(tokenString, curry);
-        } catch (final Exception e) {
-          logger.warn("AUTHTOKEN_PARSING_FAILED", e);
-        }
+        return SelfValidatingAuthenticationToken.parse(tokenString);
       }
     }
     return null;
   }
 
-  private static OneTimeAuthenticationToken parseOneTimeToken(
-      final String tokenString, final String curry) {
-    if (tokenString != null && !tokenString.equals("")) {
-      try {
-        return OneTimeAuthenticationToken.parse(tokenString, curry);
-      } catch (final Exception e) {
-        logger.warn("AUTHTOKEN_PARSING_FAILED", e);
-      }
-    }
-    return null;
-  }
-
-  public static OneTimeAuthenticationToken parseOneTimeTokenQuerySegment(
-      final String tokenString, final String curry) {
-    if (tokenString != null) {
-      return parseOneTimeToken(URLDecodeWithUTF8(tokenString), curry);
-    }
-    return null;
-  }
-
-  public static OneTimeAuthenticationToken parseOneTimeTokenCookie(
-      final Cookie cookie, final String curry) {
-    if (cookie != null) {
-      return parseOneTimeToken(URLDecodeWithUTF8(cookie.getValue()), curry);
-    }
-    return null;
-  }
-
   /**
    * Create a session timeout cookie. The value is a plain UTC timestamp which tells the user how
    * long his session will stay active. This cookie will be ignored by the server and carries only
@@ -163,9 +126,7 @@ public class AuthenticationUtils {
 
     if (token != null && token.isValid()) {
       t = new Timestamp(token.getExpires()).toString().replaceFirst(" ", "T");
-      exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new
-      // expiration
-      // time.
+      exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new expiration time
       return new CookieSetting(
           0,
           AuthenticationUtils.SESSION_TIMEOUT_COOKIE,
@@ -180,6 +141,7 @@ public class AuthenticationUtils {
     return null;
   }
 
+  // TODO move
   public static boolean isResponsibleAgentExistent(final ResponsibleAgent agent) {
     // 1) check OWNER, OTHER
     if (Role.OTHER_ROLE.equals(agent) || Role.OWNER_ROLE.equals(agent)) {
@@ -227,4 +189,14 @@ public class AuthenticationUtils {
         false,
         false);
   }
+
+  public static Collection<String> getRoles(Subject user) {
+    return new CaosDBAuthorizingRealm().doGetAuthorizationInfo(user.getPrincipals()).getRoles();
+  }
+
+  public static boolean isFromOneTimeTokenRealm(Subject subject) {
+    return ((Principal) subject.getPrincipal())
+        .getRealm()
+        .equals(OneTimeAuthenticationToken.REALM_NAME);
+  }
 }
diff --git a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java
index 5cfa425ac235405a0c861e54c9d97ae8ffab58f5..abd52d9ed306f43c51d8bf52bdf1c25d86d8454e 100644
--- a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java
+++ b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java
@@ -22,9 +22,7 @@
  */
 package caosdb.server.accessControl;
 
-import com.google.common.base.Objects;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
 import java.util.Set;
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
@@ -32,100 +30,47 @@ import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.authz.SimpleAuthorizationInfo;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.subject.PrincipalCollection;
-import org.apache.shiro.subject.SimplePrincipalCollection;
 
 public class CaosDBAuthorizingRealm extends AuthorizingRealm {
 
-  private static class PermissionPrincipalCollection extends SimplePrincipalCollection {
-
-    private final List<String> permissions;
-
-    public PermissionPrincipalCollection(
-        final PrincipalCollection principals, final String[] permissions) {
-      super(principals);
-      this.permissions = Arrays.asList(permissions);
-    }
-
-    private static final long serialVersionUID = 5585425107072564933L;
-
-    @Override
-    public boolean equals(final Object obj) {
-      if (obj == this) {
-        return true;
-      } else if (obj instanceof PermissionPrincipalCollection) {
-        final PermissionPrincipalCollection that = (PermissionPrincipalCollection) obj;
-        return Objects.equal(that.permissions, this.permissions) && super.equals(that);
-      } else {
-        return false;
-      }
-    }
+  private static final CaosDBRolePermissionResolver role_permission_resolver =
+      new CaosDBRolePermissionResolver();
 
-    @Override
-    public int hashCode() {
-      return super.hashCode() + 28 * this.permissions.hashCode();
-    }
+  public Collection<String> getSessionRoles(SelfValidatingAuthenticationToken token) {
+    return token.getRoles();
   }
 
-  static class PermissionAuthenticationInfo implements AuthenticationInfo {
-
-    private static final long serialVersionUID = -3714484164124767976L;
-    private final PermissionPrincipalCollection principalCollection;
-
-    public PermissionAuthenticationInfo(
-        final PrincipalCollection principals, final String... permissions) {
-      this.principalCollection = new PermissionPrincipalCollection(principals, permissions);
-    }
+  public Collection<String> getSessionPermissions(SelfValidatingAuthenticationToken token) {
+    return token.getPermissions();
+  }
 
-    @Override
-    public PrincipalCollection getPrincipals() {
-      return this.principalCollection;
-    }
+  @Override
+  protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
+    final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
+    Object principal = principals.getPrimaryPrincipal();
 
-    @Override
-    public Object getCredentials() {
-      return null;
-    }
+    // Add explicitly given roles and permissions.
+    if (principal instanceof SelfValidatingAuthenticationToken) {
+      Collection<String> sessionPermissions =
+          getSessionPermissions((SelfValidatingAuthenticationToken) principal);
 
-    @Override
-    public boolean equals(final Object obj) {
-      if (obj == this) {
-        return true;
-      } else if (obj instanceof PermissionAuthenticationInfo) {
-        final PermissionAuthenticationInfo that = (PermissionAuthenticationInfo) obj;
-        return Objects.equal(that.principalCollection, this.principalCollection);
-      } else {
-        return false;
-      }
-    }
+      Collection<String> sessionRoles =
+          getSessionRoles((SelfValidatingAuthenticationToken) principal);
 
-    @Override
-    public int hashCode() {
-      return this.principalCollection.hashCode();
+      authzInfo.addRoles(sessionRoles);
+      authzInfo.addStringPermissions(sessionPermissions);
     }
-  }
-
-  private static final CaosDBRolePermissionResolver role_permission_resolver =
-      new CaosDBRolePermissionResolver();
 
-  @Override
-  protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
-    if (principals instanceof PermissionPrincipalCollection) {
-      // the PrincialsCollection carries the permissions.
-      final SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
-      info.addStringPermissions(((PermissionPrincipalCollection) principals).permissions);
-      return info;
+    // Find all roles which are associated with this principal in this realm.
+    final Set<String> principalRoles =
+        UserSources.resolve((Principal) principals.getPrimaryPrincipal());
+    if (principalRoles != null) {
+      authzInfo.addRoles(principalRoles);
     }
 
-    final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
-
-    // find all roles which are associated with this principal in this
-    // realm.
-    final Set<String> roles = UserSources.resolve(principals);
-    if (roles != null) {
-      authzInfo.setRoles(roles);
-
-      // find all permissions which are associated with these roles.
-      authzInfo.addObjectPermission(role_permission_resolver.resolvePermissionsInRole(roles));
+    if (authzInfo.getRoles() != null && !authzInfo.getRoles().isEmpty()) {
+      authzInfo.addObjectPermission(
+          role_permission_resolver.resolvePermissionsInRole(authzInfo.getRoles()));
     }
 
     return authzInfo;
diff --git a/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java b/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java
index 3350a68700f108e8ed5cf7309e9f305ac0bb669b..ae4c38605f07f5222499e076def3a3d48c31a385 100644
--- a/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java
+++ b/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java
@@ -33,6 +33,7 @@ import org.apache.shiro.authc.AuthenticationException;
 
 public class CaosDBRolePermissionResolver {
 
+  /** Return CaosPermission with the rules which are associated with the roles. */
   public CaosPermission resolvePermissionsInRole(final Set<String> roles) {
     final HashSet<PermissionRule> rules = new HashSet<PermissionRule>();
     for (final String role : roles) {
diff --git a/src/main/java/caosdb/server/accessControl/Config.java b/src/main/java/caosdb/server/accessControl/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..3da7a426a9dcecfb93264cb9e504a157b10659c8
--- /dev/null
+++ b/src/main/java/caosdb/server/accessControl/Config.java
@@ -0,0 +1,86 @@
+package caosdb.server.accessControl;
+
+public class Config {
+  private String[] permissions = {};
+  private String[] roles = {};
+  private String purpose = null;
+  private OneTimeTokenToFile output = null;
+  private int maxReplays = 1;
+  private long expiresAfter = OneTimeAuthenticationToken.DEFAULT_TIMEOUT_MS;
+  private long replayTimeout = OneTimeAuthenticationToken.DEFAULT_REPLAYS_TIMEOUT_MS;
+  private String name = AnonymousAuthenticationToken.PRINCIPAL.getUsername();
+
+  public Config() {}
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public long getExpiresAfter() {
+    return expiresAfter;
+  }
+
+  public void setExpiresAfter(long timeout) {
+    this.expiresAfter = timeout;
+  }
+
+  public void setReplayTimeoutSeconds(long seconds) {
+    this.setReplayTimeout(seconds * 1000);
+  }
+
+  public void setExpiresAfterSeconds(long seconds) {
+    this.setExpiresAfter(seconds * 1000);
+  }
+
+  public void setMaxReplays(int maxReplays) {
+    this.maxReplays = maxReplays;
+  }
+
+  public int getMaxReplays() {
+    return maxReplays;
+  }
+
+  public String[] getPermissions() {
+    return permissions;
+  }
+
+  public String getPurpose() {
+    return purpose;
+  }
+
+  public void setPermissions(String[] permissions) {
+    this.permissions = permissions;
+  }
+
+  public String[] getRoles() {
+    return roles;
+  }
+
+  public void setRoles(String[] roles) {
+    this.roles = roles;
+  }
+
+  public void setPurpose(String purpose) {
+    this.purpose = purpose;
+  }
+
+  public OneTimeTokenToFile getOutput() {
+    return output;
+  }
+
+  public void setOutput(OneTimeTokenToFile output) {
+    this.output = output;
+  }
+
+  public long getReplayTimeout() {
+    return replayTimeout;
+  }
+
+  public void setReplayTimeout(long replaysTimeout) {
+    this.replayTimeout = replaysTimeout;
+  }
+}
diff --git a/src/main/java/caosdb/server/accessControl/ConsumedInfoCleanupJob.java b/src/main/java/caosdb/server/accessControl/ConsumedInfoCleanupJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f62fa0a8fc72604d885c24123c3d0c17cb49a0a
--- /dev/null
+++ b/src/main/java/caosdb/server/accessControl/ConsumedInfoCleanupJob.java
@@ -0,0 +1,29 @@
+package caosdb.server.accessControl;
+
+import caosdb.server.CaosDBServer;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+
+public class ConsumedInfoCleanupJob implements Job {
+
+  public static void scheduleDaily() throws SchedulerException {
+    JobDetail job = JobBuilder.newJob(ConsumedInfoCleanupJob.class).build();
+    Trigger trigger =
+        TriggerBuilder.newTrigger()
+            .withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(0, 0))
+            .build();
+    CaosDBServer.scheduleJob(job, trigger);
+  }
+
+  @Override
+  public void execute(JobExecutionContext context) throws JobExecutionException {
+    OneTimeTokenConsumedInfo.cleanupConsumedInfo();
+  }
+}
diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java
index ba7dff8d50bd5f77ae4dc035e07caef48d6f3426..8dddf77b092cb951695ecaa15191856a3909793f 100644
--- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java
+++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -24,112 +26,240 @@ package caosdb.server.accessControl;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
-import java.util.UUID;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.apache.shiro.subject.Subject;
 import org.eclipse.jetty.util.ajax.JSON;
+import org.quartz.SchedulerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken {
 
-  private static final transient String PEPPER = java.util.UUID.randomUUID().toString();
-  private final String[] permissions;
+  public static final long DEFAULT_MAX_REPLAYS = 1L;
+  public static final int DEFAULT_REPLAYS_TIMEOUT_MS =
+      Integer.parseInt(
+          CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS));
+  public static final int DEFAULT_TIMEOUT_MS =
+      Integer.parseInt(
+          CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS));
+  public static final String REALM_NAME = "OneTimeAuthenticationToken"; // TODO move to UserSources
+  public static final Logger LOGGER = LoggerFactory.getLogger(OneTimeAuthenticationToken.class);
+
+  private long maxReplays;
+  private long replaysTimeout;
 
   public OneTimeAuthenticationToken(
       final Principal principal,
       final long date,
       final long timeout,
       final String salt,
-      final String curry,
       final String checksum,
-      final String... permissions) {
-    super(principal, date, timeout, salt, curry, checksum);
-    this.permissions = permissions;
+      final String[] permissions,
+      final String[] roles,
+      final long maxReplays,
+      final long replaysTimeout) {
+    super(principal, date, timeout, salt, checksum, permissions, roles);
+    this.replaysTimeout = replaysTimeout;
+    this.maxReplays = maxReplays;
+    consume();
+  }
+
+  public long getReplaysTimeout() {
+    return replaysTimeout;
+  }
+
+  public void consume() {
+    OneTimeTokenConsumedInfo.consume(this);
   }
 
   public OneTimeAuthenticationToken(
       final Principal principal,
-      final long date,
       final long timeout,
-      final String salt,
-      final String curry,
-      final String... permissions) {
+      final String[] permissions,
+      final String[] roles,
+      final Long maxReplays,
+      final Long replaysTimeout) {
     super(
         principal,
-        date,
         timeout,
-        salt,
-        curry,
-        calcChecksum(
-            principal.getRealm(),
-            principal.getUsername(),
-            date,
-            timeout,
-            salt,
-            curry,
-            calcChecksum((Object[]) permissions),
-            PEPPER));
-    this.permissions = permissions;
+        permissions,
+        roles,
+        defaultIfNull(maxReplays, DEFAULT_MAX_REPLAYS),
+        defaultIfNull(replaysTimeout, DEFAULT_REPLAYS_TIMEOUT_MS));
   }
 
   private static final long serialVersionUID = -1072740888045267613L;
 
-  public String[] getPermissions() {
-    return this.permissions;
+  /**
+   * Return consumed.
+   *
+   * @param array
+   * @param curry
+   * @return
+   */
+  public static OneTimeAuthenticationToken parse(final Object[] array) {
+    final Principal principal = new Principal((String) array[1], (String) array[2]);
+    final String[] roles = toStringArray((Object[]) array[3]);
+    final String[] permissions = toStringArray((Object[]) array[4]);
+    final long date = (Long) array[5];
+    final long timeout = (Long) array[6];
+    final String salt = (String) array[7];
+    final String checksum = (String) array[8];
+    final long maxReplays = (Long) array[9];
+    final long replaysTimeout = (Long) array[10];
+    return new OneTimeAuthenticationToken(
+        principal, date, timeout, salt, checksum, permissions, roles, maxReplays, replaysTimeout);
+  }
+
+  private static OneTimeAuthenticationToken generate(
+      final Principal principal,
+      final String[] permissions,
+      final String[] roles,
+      final long timeout,
+      final long maxReplays,
+      final long replaysTimeout) {
+
+    return new OneTimeAuthenticationToken(
+        principal, timeout, permissions, roles, maxReplays, replaysTimeout);
+  }
+
+  public static List<Config> loadConfig(InputStream input) throws Exception {
+    List<Config> results = new LinkedList<>();
+    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+    ObjectReader reader = mapper.readerFor(Config.class);
+    Iterator<Config> configs = reader.readValues(input);
+    configs.forEachRemaining(results::add);
+    return results;
+  }
+
+  public static Map<String, Config> getPurposeMap(List<Config> configs) throws Exception {
+    Map<String, Config> result = new HashMap<>();
+    for (Config config : configs) {
+      if (config.getPurpose() != null && !config.getPurpose().isEmpty()) {
+        if (result.containsKey(config.getPurpose())) {
+          throw new Exception(
+              "OneTimeAuthToken configuration contains duplicate values for the 'purpose' property.");
+        }
+        result.put(config.getPurpose(), config);
+      }
+    }
+    return result;
+  }
+
+  public static OneTimeAuthenticationToken generate(Config c) {
+    return generate(c, new Principal(REALM_NAME, c.getName()));
+  }
+
+  public static OneTimeAuthenticationToken generateForPurpose(String purpose, Subject user) {
+    Config c = purposes.get(purpose);
+    if (c != null) {
+      Principal principal = (Principal) user.getPrincipal();
+
+      return generate(c, principal);
+    }
+    return null;
+  }
+
+  public static OneTimeAuthenticationToken generate(Config c, Principal principal) {
+    return generate(
+        principal,
+        c.getPermissions(),
+        c.getRoles(),
+        c.getExpiresAfter(),
+        c.getMaxReplays(),
+        c.getReplayTimeout());
+  }
+
+  static Map<String, Config> purposes = new HashMap<>();
+
+  public static Map<String, Config> getPurposeMap() {
+    return purposes;
+  }
+
+  public static void initConfig(InputStream yamlConfig) throws Exception {
+    List<Config> configs = loadConfig(yamlConfig);
+    initOutput(configs);
+    purposes.putAll(getPurposeMap(configs));
+  }
+
+  private static void initOutput(List<Config> configs) throws IOException, SchedulerException {
+    for (Config config : configs) {
+      if (config.getOutput() != null) {
+        config.getOutput().init(config);
+      }
+    }
+  }
+
+  public static void initConfig() throws Exception {
+    resetConfig();
+    try (FileInputStream f =
+        new FileInputStream(
+            CaosDBServer.getServerProperty(ServerProperties.KEY_AUTHTOKEN_CONFIG))) {
+      initConfig(f);
+    } catch (IOException e) {
+      LOGGER.error("Could not load the auth token configuration", e);
+    }
   }
 
   @Override
-  public String calcChecksum() {
+  protected void setFields(Object[] fields) {
+    if (fields.length == 2) {
+      this.maxReplays = (long) fields[0];
+      this.replaysTimeout = (long) fields[1];
+    } else {
+      throw new InstantiationError("Too few fields.");
+    }
+  }
+
+  @Override
+  public String calcChecksum(String pepper) {
     return calcChecksum(
-        this.principal.getRealm(),
-        this.principal.getUsername(),
+        "O",
+        this.getRealm(),
+        this.getUsername(),
         this.date,
         this.timeout,
         this.salt,
-        this.curry,
         calcChecksum((Object[]) this.permissions),
-        PEPPER);
+        calcChecksum((Object[]) this.roles),
+        this.maxReplays,
+        this.replaysTimeout,
+        pepper);
   }
 
   @Override
   public String toString() {
     return JSON.toString(
         new Object[] {
-          this.principal.getRealm(),
-          this.principal.getUsername(),
+          "O",
+          this.getRealm(),
+          this.getUsername(),
+          this.roles,
+          this.permissions,
           this.date,
           this.timeout,
           this.salt,
-          this.permissions,
-          this.checksum
+          this.checksum,
+          this.maxReplays,
+          this.replaysTimeout
         });
   }
 
-  private static String[] toStringArray(final Object[] array) {
-    final String[] ret = new String[array.length];
-    for (int i = 0; i < ret.length; i++) {
-      ret[i] = (String) array[i];
-    }
-    return ret;
-  }
-
-  public static OneTimeAuthenticationToken parse(final String token, final String curry) {
-    final Object[] array = (Object[]) JSON.parse(token);
-    final Principal principal = new Principal((String) array[0], (String) array[1]);
-    final long date = (Long) array[2];
-    final long timeout = (Long) array[3];
-    final String salt = (String) array[4];
-    final String[] permissions = toStringArray((Object[]) array[5]);
-    final String checksum = (String) array[6];
-    return new OneTimeAuthenticationToken(
-        principal, date, timeout, salt, curry, checksum, permissions);
+  public long getMaxReplays() {
+    return maxReplays;
   }
 
-  public static OneTimeAuthenticationToken generate(
-      final Principal principal, final String curry, final String... permissions) {
-    return new OneTimeAuthenticationToken(
-        principal,
-        System.currentTimeMillis(),
-        Long.parseLong(CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)),
-        UUID.randomUUID().toString(),
-        curry,
-        permissions);
+  public static void resetConfig() {
+    purposes.clear();
   }
 }
diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8ebd2c807712d8b06ec68771e7abb8082ce0696
--- /dev/null
+++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java
@@ -0,0 +1,89 @@
+package caosdb.server.accessControl;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.apache.shiro.authc.AuthenticationException;
+
+/**
+ * Utility class to manage OTTs: mark as consumed, removed expired OTTs, manage maximum number of
+ * replays and replay timeout of tokens.
+ */
+class OneTimeTokenConsumedInfo {
+
+  private static Map<String, OneTimeTokenConsumedInfo> consumedOneTimeTokens = new HashMap<>();
+
+  public static void cleanupConsumedInfo() {
+    synchronized (consumedOneTimeTokens) {
+      for (Iterator<Map.Entry<String, OneTimeTokenConsumedInfo>> it =
+              consumedOneTimeTokens.entrySet().iterator();
+          it.hasNext(); ) {
+        Map.Entry<String, OneTimeTokenConsumedInfo> next = it.next();
+        if (next.getValue().isExpired()) {
+          it.remove();
+        }
+      }
+    }
+  }
+
+  /** If the token is valid, consume it once and store this information. */
+  public static void consume(OneTimeAuthenticationToken oneTimeAuthenticationToken) {
+    if (oneTimeAuthenticationToken.isValid()) {
+      String key = OneTimeTokenConsumedInfo.getKey(oneTimeAuthenticationToken);
+      OneTimeTokenConsumedInfo consumedInfo = null;
+      synchronized (consumedOneTimeTokens) {
+        consumedInfo = consumedOneTimeTokens.get(key);
+        if (consumedInfo == null) {
+          consumedInfo = new OneTimeTokenConsumedInfo(oneTimeAuthenticationToken);
+          consumedOneTimeTokens.put(key, consumedInfo);
+        }
+      }
+      consumedInfo.consume();
+    }
+  }
+
+  private OneTimeAuthenticationToken oneTimeAuthenticationToken;
+  private List<Long> replays = new LinkedList<>();
+
+  public OneTimeTokenConsumedInfo(OneTimeAuthenticationToken oneTimeAuthenticationToken) {
+    this.oneTimeAuthenticationToken = oneTimeAuthenticationToken;
+  }
+
+  public static String getKey(OneTimeAuthenticationToken token) {
+    return token.checksum;
+  }
+
+  private int getNoOfReplays() {
+    return replays.size();
+  }
+
+  private long getMaxReplays() {
+    return oneTimeAuthenticationToken.getMaxReplays();
+  }
+
+  private long getReplayTimeout() {
+    if (replays.size() == 0) {
+      return Long.MAX_VALUE;
+    }
+    long firstReplayTime = replays.get(0);
+    return firstReplayTime + oneTimeAuthenticationToken.getReplaysTimeout();
+  }
+
+  /** If there are still replays and time left, increase the replay counter by one. */
+  public void consume() {
+    synchronized (replays) {
+      if (getNoOfReplays() >= getMaxReplays()) {
+        throw new AuthenticationException("One-time token was consumed too often.");
+      } else if (getReplayTimeout() < System.currentTimeMillis()) {
+        throw new AuthenticationException("One-time token replays timeout expired.");
+      }
+      replays.add(System.currentTimeMillis());
+    }
+  }
+
+  public boolean isExpired() {
+    return oneTimeAuthenticationToken.isExpired();
+  }
+}
diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java
deleted file mode 100644
index 468a8d8d212ca5546f1940a4b3de38c1da50b3d8..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.accessControl;
-
-import caosdb.server.accessControl.CaosDBAuthorizingRealm.PermissionAuthenticationInfo;
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
-
-public class OneTimeTokenRealm extends SessionTokenRealm {
-
-  @Override
-  protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
-      throws AuthenticationException {
-
-    final AuthenticationInfo info = super.doGetAuthenticationInfo(token);
-    if (info != null) {
-      return new PermissionAuthenticationInfo(
-          info.getPrincipals(), ((OneTimeAuthenticationToken) token).getPermissions());
-    }
-
-    return null;
-  }
-
-  public OneTimeTokenRealm() {
-    setAuthenticationTokenClass(OneTimeAuthenticationToken.class);
-    setCredentialsMatcher(new AllowAllCredentialsMatcher());
-    setCachingEnabled(false);
-    setAuthenticationCachingEnabled(false);
-    // setAuthorizationCachingEnabled(false);
-  }
-}
diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4006783a2105a353749edfaa475bde3dae93808
--- /dev/null
+++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java
@@ -0,0 +1,83 @@
+package caosdb.server.accessControl;
+
+import caosdb.server.CaosDBServer;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+
+public class OneTimeTokenToFile implements Job {
+  private String file = null;
+  private String schedule = null;
+
+  public OneTimeTokenToFile() {}
+
+  public static void output(OneTimeAuthenticationToken t, String file) throws IOException {
+    output(t, new File(file));
+  }
+
+  public static void output(OneTimeAuthenticationToken t, File file) throws IOException {
+    Files.createParentDirs(file);
+    try (PrintWriter writer = new PrintWriter(file, "utf-8")) {
+      writer.print(t.toString());
+    }
+  }
+
+  public String getFile() {
+    return file;
+  }
+
+  public void setFile(String file) {
+    this.file = file;
+  }
+
+  public String getSchedule() {
+    return schedule;
+  }
+
+  public void setSchedule(String schedule) {
+    this.schedule = schedule;
+  }
+
+  /** If no schedule was set, immediately write the config to file, else schedule the job. */
+  public void init(Config config) throws IOException, SchedulerException {
+
+    if (this.schedule != null) {
+      OneTimeAuthenticationToken.generate(config); // test config, throw away token
+      JobDataMap map = new JobDataMap();
+      map.put("config", config);
+      map.put("file", file);
+      JobDetail outputJob = JobBuilder.newJob(OneTimeTokenToFile.class).setJobData(map).build();
+      Trigger trigger =
+          TriggerBuilder.newTrigger()
+              .withIdentity(config.toString())
+              .withSchedule(CronScheduleBuilder.cronSchedule(this.schedule))
+              .build();
+      CaosDBServer.scheduleJob(outputJob, trigger);
+    } else {
+      output(OneTimeAuthenticationToken.generate(config), file);
+    }
+  }
+
+  @Override
+  public void execute(JobExecutionContext context) throws JobExecutionException {
+    Config config = (Config) context.getMergedJobDataMap().get("config");
+    String file = context.getMergedJobDataMap().getString("file");
+    try {
+      output(OneTimeAuthenticationToken.generate(config), file);
+    } catch (IOException e) {
+      // TODO log
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/src/main/java/caosdb/server/accessControl/Principal.java b/src/main/java/caosdb/server/accessControl/Principal.java
index 3183d864bd5baecc4429cf22fc0ab3ca75d8ca15..6a95dd79eccd50c9358928909822af67056102db 100644
--- a/src/main/java/caosdb/server/accessControl/Principal.java
+++ b/src/main/java/caosdb/server/accessControl/Principal.java
@@ -47,6 +47,11 @@ public class Principal implements ResponsibleAgent {
     this(split[0], split[1]);
   }
 
+  public Principal(Principal principal) {
+    this.username = principal.username;
+    this.realm = principal.realm;
+  }
+
   public String getRealm() {
     return this.realm;
   }
diff --git a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java
index 079f592ea7911f974bf4a64c9c5f1674e64a09a5..119a86248b83a472df0d2981db43d9a6cc1962f0 100644
--- a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java
+++ b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,52 +25,139 @@
 package caosdb.server.accessControl;
 
 import caosdb.server.utils.Utils;
+import java.util.Arrays;
+import java.util.Collection;
+import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.AuthenticationToken;
+import org.eclipse.jetty.util.ajax.JSON;
 
-public abstract class SelfValidatingAuthenticationToken implements AuthenticationToken {
+/**
+ * These AuthenticationTokens are characterized by the following properties:
+ *
+ * <ul>
+ *   <li>date: The creation time.
+ *   <li>timeout: How long this token is valid after creation.
+ *   <li>checksum: The checksum is calculated from all relevant parts of the authentication token
+ *       (including the salt, timeout, permissions, roles, and date) and most importantly, the
+ *       pepper which serves as a randomized password of the server. The salt makes it hard to guess
+ *       the pepper by creating a rainbow table with plausible values for the other properties.
+ *   <li>salt: Salt for the password checksum, may be used by inheriting classes.
+ *   <li>pepper: A static property, generated when class is loaded and used until the server
+ *       reboots. It servers as randomized password of the server. "In cryptography, a pepper is a
+ *       secret added to an input such as a password prior to being hashed with a cryptographic hash
+ *       function." (from: Pepper (cryptography),
+ *       https://en.wikipedia.org/w/index.php?title=Pepper_(cryptography)&oldid=960047694 (last
+ *       visited July 7, 2020)) In our case, the pepper is added to the token before hashing, but
+ *       not exposed to the public, while the salt is. That also means that the resulting hash
+ *       cannot be generated by any client nor be validated by any client, and that all tokens of
+ *       this kind invalidate when the server reboots.
+ */
+public abstract class SelfValidatingAuthenticationToken extends Principal
+    implements AuthenticationToken {
 
+  protected static final transient String PEPPER = Utils.getSecureFilename(32);
   private static final long serialVersionUID = -7212039848895531161L;
+  // date is the token creation time, in ms since 1970
   protected final long date;
+  // token validity duration
   protected final long timeout;
   protected final String checksum;
-  protected final Principal principal;
-  protected final String curry;
   protected final String salt;
+  protected final String[] permissions;
+  protected final String[] roles;
+
+  public Collection<String> getPermissions() {
+    return Arrays.asList(this.permissions);
+  }
+
+  public Collection<String> getRoles() {
+    return Arrays.asList(this.roles);
+  }
+
+  public static final String getFreshSalt() {
+    // salt should be at least 8 octets long. https://www.ietf.org/rfc/rfc2898.txt
+    // let's double that
+    return Utils.getSecureFilename(16);
+  }
+
+  protected static <T> T defaultIfNull(T val, T def) {
+    if (val != null) {
+      return val;
+    }
+    return def;
+  }
 
   public SelfValidatingAuthenticationToken(
       final Principal principal,
       final long date,
       final long timeout,
       final String salt,
-      final String curry,
-      final String checksum) {
-    this.date = date;
-    this.timeout = timeout;
-    this.principal = principal;
-    this.salt = salt;
-    this.curry = curry;
-    this.checksum = checksum;
+      final String checksum,
+      final String[] permissions,
+      final String[] roles) {
+    this(principal, date, timeout, salt, permissions, roles, checksum, false);
   }
 
-  public SelfValidatingAuthenticationToken(
+  private SelfValidatingAuthenticationToken(
       final Principal principal,
       final long date,
       final long timeout,
       final String salt,
-      final String curry) {
+      final String[] permissions,
+      final String[] roles,
+      String checksum,
+      boolean newChecksum,
+      Object... fields) {
+    super(principal);
     this.date = date;
     this.timeout = timeout;
-    this.principal = principal;
     this.salt = salt;
-    this.curry = curry;
-    this.checksum = calcChecksum();
+    this.permissions = defaultIfNull(permissions, new String[] {});
+    this.roles = defaultIfNull(roles, new String[] {});
+    if (fields.length > 0) setFields(fields);
+    // only calculate a checksum iff none is given and newChecksum is explicitly 'true'.
+    this.checksum = checksum == null && newChecksum ? calcChecksum() : checksum;
   }
 
-  @Override
-  public Principal getPrincipal() {
-    return this.principal;
+  /** Customizable customization method, will be called with the remaining constructor arguments. */
+  protected abstract void setFields(Object[] fields);
+
+  public SelfValidatingAuthenticationToken(
+      final Principal principal,
+      final long timeout,
+      final String[] permissions,
+      final String[] roles,
+      Object... fields) {
+    this(
+        principal,
+        System.currentTimeMillis(),
+        timeout,
+        getFreshSalt(),
+        permissions,
+        roles,
+        null,
+        true,
+        fields);
+  }
+
+  public final String calcChecksum() {
+    return calcChecksum(PEPPER);
   }
 
+  @Override
+  public abstract String toString();
+
+  /**
+   * Implementation specific version of a peppered checksum.
+   *
+   * <p>For secure operation, implementing classes must make sure that the pepper is actually used
+   * in calculating the checksum and that the checksum can not be used to infer information about
+   * the pepper. This can be achieved for example by using the {@link calcChecksum(final Object...
+   * fields)} method.
+   */
+  public abstract String calcChecksum(String pepper);
+
+  /** No credentials (returns null), since this token is self-validating. */
   @Override
   public Object getCredentials() {
     return null;
@@ -86,6 +175,9 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio
     return System.currentTimeMillis() >= getExpires();
   }
 
+  /**
+   * Test if the hash stored in `checksum` is equal to the one calculated using the secret pepper.
+   */
   public boolean isHashValid() {
     final String other = calcChecksum();
     return this.checksum != null && this.checksum.equals(other);
@@ -95,8 +187,7 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio
     return !isExpired() && isHashValid();
   }
 
-  public abstract String calcChecksum();
-
+  /** Return the hash (SHA512) of the stringified arguments. */
   protected static String calcChecksum(final Object... fields) {
     final StringBuilder sb = new StringBuilder();
     for (final Object field : fields) {
@@ -107,4 +198,36 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio
     }
     return Utils.sha512(sb.toString(), null, 1);
   }
+
+  protected static String[] toStringArray(final Object[] array) {
+    final String[] ret = new String[array.length];
+    for (int i = 0; i < ret.length; i++) {
+      ret[i] = (String) array[i];
+    }
+    return ret;
+  }
+
+  /**
+   * Parse a JSON string and return the generated token. Depending on the first element of the JSON,
+   * this is either (if it is "O") a OneTimeAuthenticationToken or (if it is "S") a SessionToken.
+   *
+   * @throws AuthenticationToken if the string could not be parsed into a token.
+   */
+  public static SelfValidatingAuthenticationToken parse(String token) {
+    Object[] array = (Object[]) JSON.parse(token);
+    switch (array[0].toString()) {
+      case "O":
+        return OneTimeAuthenticationToken.parse(array);
+      case "S":
+        return SessionToken.parse(array);
+      default:
+        throw new AuthenticationException("Could not parse the authtoken string (unknown type).");
+    }
+  }
+
+  /** No "other" identity, so this returns itself. */
+  @Override
+  public SelfValidatingAuthenticationToken getPrincipal() {
+    return this;
+  }
 }
diff --git a/src/main/java/caosdb/server/accessControl/SessionToken.java b/src/main/java/caosdb/server/accessControl/SessionToken.java
index 273bd50ba7c4ef89fecd0e2b9b8817acbf6d9efa..d24b4afeb739d764947092783c544168a6c0cf3f 100644
--- a/src/main/java/caosdb/server/accessControl/SessionToken.java
+++ b/src/main/java/caosdb/server/accessControl/SessionToken.java
@@ -4,6 +4,9 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -24,86 +27,109 @@ package caosdb.server.accessControl;
 
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
+import org.apache.shiro.subject.Subject;
 import org.eclipse.jetty.util.ajax.JSON;
 
+/**
+ * Session tokens are formatted as JSON arrays with the following elements:
+ *
+ * <ul>
+ *   <li>Anything but "O" (upper-case "o"), preferred is "S".
+ *   <li>Realm
+ *   <li>name within the Realm
+ *   <li>list of roles
+ *   <li>list of permissions
+ *   <li>time of token generation (long, ms since 1970)
+ *   <li>validity duration (long, ms)
+ *   <li>salt
+ *   <li>checksum
+ */
 public class SessionToken extends SelfValidatingAuthenticationToken {
 
-  /**
-   * Cryptographic pepper. Generated when class is loaded and used until the server reboots. Hence
-   * all SessionTokens invalidate when the server reboots.
-   */
-  private static final transient String PEPPER = java.util.UUID.randomUUID().toString();
-
-  public static final String SEP = ":";
-
   public SessionToken(
       final Principal principal,
       final long date,
       final long timeout,
       final String salt,
-      final String curry,
-      final String checksum) {
-    super(principal, date, timeout, salt, curry, checksum);
+      final String checksum,
+      final String[] permissions,
+      final String[] roles) {
+    super(principal, date, timeout, salt, checksum, permissions, roles);
   }
 
   public SessionToken(
       final Principal principal,
-      final long date,
       final long timeout,
-      final String salt,
-      final String curry) {
-    super(principal, date, timeout, salt, curry);
+      final String[] permissions,
+      final String[] roles) {
+    super(principal, timeout, permissions, roles);
   }
 
   private static final long serialVersionUID = 5887135104218573761L;
 
+  public static SessionToken parse(final Object[] array) {
+    // array[0] is not used here, it was already consumed to determine the type of token.
+    final Principal principal = new Principal((String) array[1], (String) array[2]);
+    final String[] roles = toStringArray((Object[]) array[3]);
+    final String[] permissions = toStringArray((Object[]) array[4]);
+    final long date = (Long) array[5];
+    final long timeout = (Long) array[6];
+    final String salt = (String) array[7];
+    final String checksum = (String) array[8];
+    return new SessionToken(principal, date, timeout, salt, checksum, permissions, roles);
+  }
+
+  private static SessionToken generate(
+      final Principal principal, final String[] permissions, final String[] roles) {
+    int timeout =
+        Integer.parseInt(CaosDBServer.getServerProperty(ServerProperties.KEY_SESSION_TIMEOUT_MS));
+
+    return new SessionToken(principal, timeout, permissions, roles);
+  }
+
+  public static SessionToken generate(Subject subject) {
+    String[] permissions = new String[] {};
+    String[] roles = new String[] {};
+    if (subject.getPrincipal() instanceof SelfValidatingAuthenticationToken) {
+      SelfValidatingAuthenticationToken p =
+          (SelfValidatingAuthenticationToken) subject.getPrincipal();
+      permissions = p.getPermissions().toArray(permissions);
+      roles = p.getRoles().toArray(roles);
+    }
+    return generate((Principal) subject.getPrincipal(), permissions, roles);
+  }
+
+  /** Nothing to set in this implemention. */
+  @Override
+  protected void setFields(Object[] fields) {}
+
   @Override
-  public String calcChecksum() {
+  public String calcChecksum(String pepper) {
     return calcChecksum(
+        "S",
+        this.getRealm(),
+        this.getUsername(),
         this.date,
         this.timeout,
-        this.principal.getRealm(),
-        this.principal.getUsername(),
-        PEPPER,
         this.salt,
-        this.curry);
+        calcChecksum((Object[]) this.permissions),
+        calcChecksum((Object[]) this.roles),
+        pepper);
   }
 
   @Override
   public String toString() {
     return JSON.toString(
         new Object[] {
-          this.principal.getRealm(),
-          this.principal.getUsername(),
+          "S",
+          this.getRealm(),
+          this.getUsername(),
+          this.roles,
+          this.permissions,
           this.date,
           this.timeout,
           this.salt,
           this.checksum
         });
   }
-
-  public static SessionToken parse(final String token, final String curry) {
-    final Object[] array = (Object[]) JSON.parse(token);
-    final Principal principal = new Principal((String) array[0], (String) array[1]);
-    final long date = (Long) array[2];
-    final long timeout = (Long) array[3];
-    final String salt = (String) array[4];
-    final String checksum = (String) array[5];
-    return new SessionToken(principal, date, timeout, salt, curry, checksum);
-  }
-
-  public static SessionToken generate(final Principal principal, final String curry) {
-    final SessionToken ret =
-        new SessionToken(
-            principal,
-            System.currentTimeMillis(),
-            Long.parseLong(
-                CaosDBServer.getServerProperty(ServerProperties.KEY_SESSION_TIMEOUT_MS).trim()),
-            java.util.UUID.randomUUID().toString(),
-            curry);
-    if (!ret.isValid()) {
-      throw new RuntimeException("SessionToken not valid!");
-    }
-    return ret;
-  }
 }
diff --git a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java
index 6ee72d0295153051e5ad31a6fd0fa092ab53d6e3..cce2850d6d532ae8880d208682b5677dc0369089 100644
--- a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java
+++ b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java
@@ -37,13 +37,13 @@ public class SessionTokenRealm extends AuthenticatingRealm {
         (SelfValidatingAuthenticationToken) token;
 
     if (sessionToken.isValid()) {
-      return new SimpleAuthenticationInfo(sessionToken.getPrincipal(), null, getName());
+      return new SimpleAuthenticationInfo(sessionToken, null, getName());
     }
     return null;
   }
 
   public SessionTokenRealm() {
-    setAuthenticationTokenClass(SessionToken.class);
+    setAuthenticationTokenClass(SelfValidatingAuthenticationToken.class);
     setCredentialsMatcher(new AllowAllCredentialsMatcher());
     setCachingEnabled(false);
     setAuthenticationCachingEnabled(false);
diff --git a/src/main/java/caosdb/server/accessControl/UserSources.java b/src/main/java/caosdb/server/accessControl/UserSources.java
index d0f707ebaaec8aa97e9b87d760fc0631cbd2f72a..cdf219c017608c621a59d427b2132630fef0631d 100644
--- a/src/main/java/caosdb/server/accessControl/UserSources.java
+++ b/src/main/java/caosdb/server/accessControl/UserSources.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -25,11 +27,11 @@ package caosdb.server.accessControl;
 import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
 import caosdb.server.entity.Message;
+import caosdb.server.permissions.Role;
 import caosdb.server.transaction.RetrieveRoleTransaction;
 import caosdb.server.transaction.RetrieveUserTransaction;
 import caosdb.server.utils.ServerMessages;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
@@ -37,16 +39,39 @@ import java.util.HashSet;
 import java.util.Set;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.config.Ini;
-import org.apache.shiro.subject.PrincipalCollection;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * This singleton class is the primary resource for authenticating users and resolving principals to
+ * roles.
+ *
+ * <p>Key concepts:
+ *
+ * <ul>
+ *   <li>User name: A string which identifies a user uniquely across one realm. Why is this so?
+ *       Because it is possible, that two different people from collaborating work groups with
+ *       similar names have the same user name in their group e.g. "mueller@uni1.de" and
+ *       "mueller@uni2.de" or two people from different user groups use the name "admin". In the
+ *       "mueller" example the domain name of the email is the realm of authentication.
+ *   <li>Realm: A string which uniquely identifies "where a user comes from". It guarantees the
+ *       authentication of a user with a particular user name. Currently the possible realms are
+ *       quite limited. Only "CaosDB" (which is controlled by the internal user source) and "PAM"
+ *       which delegates authentication to the host system via PAM (Pluggable Authentication Module)
+ *       are known and extension is not too easy.
+ *   <li>User Source: An instance which provides the access to a realm where users can be
+ *       authenticated.
+ *   <li>Principal: The combination of realm and user name - hence a system-wide unique identifier
+ *       for users and the primary key to identifying who did what and who is allowed to to do what.
+ * </ul>
+ *
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
 public class UserSources extends HashMap<String, UserSource> {
 
-  public static final String ANONYMOUS_ROLE = "anonymous";
   private static final Logger logger = LoggerFactory.getLogger(UserSources.class);
   public static final String KEY_DEFAULT_REALM = "defaultRealm";
-  public static final String KEY_REALMS = "defaultRealm";
+  public static final String KEY_REALMS = "realms";
   public static final String KEY_REALM_CLASS = "class";
 
   private static final long serialVersionUID = 6782744064206400521L;
@@ -61,6 +86,11 @@ public class UserSources extends HashMap<String, UserSource> {
   private UserSources() {
     initMap();
     this.put(getInternalRealm());
+    if (this.map.getSection(Ini.DEFAULT_SECTION_NAME) == null
+        || !this.map.getSection(Ini.DEFAULT_SECTION_NAME).containsKey(KEY_REALMS)) {
+      // no realms defined
+      return;
+    }
     final String[] realms =
         this.map
             .getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_REALMS)
@@ -88,6 +118,8 @@ public class UserSources extends HashMap<String, UserSource> {
     }
   }
 
+  private Ini map = null;
+
   public UserSource put(final UserSource src) {
     if (src.getName() == null) {
       throw new IllegalArgumentException("A user source's name must not be null.");
@@ -103,21 +135,14 @@ public class UserSources extends HashMap<String, UserSource> {
     return instance.put(src);
   }
 
-  private Ini map = null;
-
   public void initMap() {
-    this.map = null;
-    try {
-      final FileInputStream f =
-          new FileInputStream(
-              CaosDBServer.getServerProperty(ServerProperties.KEY_USER_SOURCES_INI_FILE));
-      this.map = new Ini();
+    this.map = new Ini();
+    try (final FileInputStream f =
+        new FileInputStream(
+            CaosDBServer.getServerProperty(ServerProperties.KEY_USER_SOURCES_INI_FILE))) {
       this.map.load(f);
-      f.close();
-    } catch (final FileNotFoundException e) {
-      e.printStackTrace();
     } catch (final IOException e) {
-      e.printStackTrace();
+      logger.debug("could not load usersources.ini", e);
     }
   }
 
@@ -126,16 +151,19 @@ public class UserSources extends HashMap<String, UserSource> {
    *
    * @param realm
    * @param username
-   * @return
+   * @return A set of user roles.
    */
-  public static Set<String> resolve(String realm, final String username) {
-
+  public static Set<String> resolveRoles(String realm, final String username) {
     if (realm == null) {
       realm = guessRealm(username);
     }
 
+    UserSource userSource = instance.get(realm);
+    if (userSource == null) {
+      return null;
+    }
     // find all roles that are associated with this principal in this realm
-    final Set<String> ret = instance.get(realm).resolveRolesForUsername(username);
+    final Set<String> ret = userSource.resolveRolesForUsername(username);
 
     return ret;
   }
@@ -165,19 +193,19 @@ public class UserSources extends HashMap<String, UserSource> {
   }
 
   public static String getDefaultRealm() {
-    return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM);
+    return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM, "CaosDB");
   }
 
-  public static Set<String> resolve(final PrincipalCollection principals) {
-    if (principals.getPrimaryPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) {
+  // @todo Refactor name: resolveRoles(...)?
+  public static Set<String> resolve(final Principal principal) {
+    if (AnonymousAuthenticationToken.PRINCIPAL == principal) {
       // anymous has one role
       Set<String> roles = new HashSet<>();
-      roles.add(ANONYMOUS_ROLE);
+      roles.add(Role.ANONYMOUS_ROLE.toString());
       return roles;
     }
 
-    Principal primaryPrincipal = (Principal) principals.getPrimaryPrincipal();
-    return resolve(primaryPrincipal.getRealm(), primaryPrincipal.getUsername());
+    return resolveRoles(principal.getRealm(), principal.getUsername());
   }
 
   public static boolean isRoleExisting(final String role) {
diff --git a/src/main/java/caosdb/server/caching/JCSCacheHelper.java b/src/main/java/caosdb/server/caching/JCSCacheHelper.java
index 1ce455a949bcfb76a36815b3b14bcea89e676a4a..c471ed7ef860d902b56940da52a876a44fd97218 100644
--- a/src/main/java/caosdb/server/caching/JCSCacheHelper.java
+++ b/src/main/java/caosdb/server/caching/JCSCacheHelper.java
@@ -5,7 +5,7 @@
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
  * Copyright (C) 2019 IndiScale GmbH
- * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+ * Copyright (C) 2019,2020 Timm Fitschen (t.fitschen@indiscale.com)
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -88,6 +88,9 @@ public class JCSCacheHelper implements CacheHelper {
       }
       logger.info("Configuring JCS Caching with {}", config);
     }
+
+    // If the JCS config is updated/reset, it has to be shut down before.
+    JCS.shutdown();
     JCS.setConfigProperties(config);
   }
 
diff --git a/src/main/java/caosdb/server/database/BackendTransaction.java b/src/main/java/caosdb/server/database/BackendTransaction.java
index 14e65e86bd9fe05b074c731e27b601fa4ed51f68..c476fb5bb6cc67aee4650fbad79e22e97bf3aec9 100644
--- a/src/main/java/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/caosdb/server/database/BackendTransaction.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH
+ * Copyright (C) 2020 Timm Fitschen (t.fitschen@indiscale.com)
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -58,6 +60,7 @@ import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole;
 import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity;
 import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveTransactionHistory;
 import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser;
+import caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory;
 import caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader;
 import caosdb.server.database.backend.implementation.MySQL.MySQLSetFileCheckedTimestampImpl;
 import caosdb.server.database.backend.implementation.MySQL.MySQLSetPassword;
@@ -113,8 +116,10 @@ import caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
 import caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl;
 import caosdb.server.database.backend.interfaces.RetrieveTransactionHistoryImpl;
 import caosdb.server.database.backend.interfaces.RetrieveUserImpl;
+import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
 import caosdb.server.database.backend.interfaces.RuleLoaderImpl;
 import caosdb.server.database.backend.interfaces.SetFileCheckedTimestampImpl;
+import caosdb.server.database.backend.interfaces.SetFileChecksumImpl;
 import caosdb.server.database.backend.interfaces.SetPasswordImpl;
 import caosdb.server.database.backend.interfaces.SetPermissionRulesImpl;
 import caosdb.server.database.backend.interfaces.SetQueryTemplateDefinitionImpl;
@@ -204,6 +209,8 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(
           RetrieveQueryTemplateDefinitionImpl.class, MySQLRetrieveQueryTemplateDefinition.class);
       setImpl(InsertEntityDatatypeImpl.class, MySQLInsertEntityDatatype.class);
+      setImpl(RetrieveVersionHistoryImpl.class, MySQLRetrieveVersionHistory.class);
+      setImpl(SetFileChecksumImpl.class, MySQLSetFileChecksum.class);
     }
   }
 
diff --git a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java
index fc4c9659d9a8ea80393bebad945bd6badf9580c4..0131b740d81cbd41d1b9263c5f42029e1af4539c 100644
--- a/src/main/java/caosdb/server/database/CacheableBackendTransaction.java
+++ b/src/main/java/caosdb/server/database/CacheableBackendTransaction.java
@@ -4,8 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- * Copyright (C) 2019 IndiScale GmbH
- * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+ * Copyright (C) 2019,2020 IndiScale GmbH
+ * Copyright (C) 2019,2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -51,7 +51,7 @@ public abstract class CacheableBackendTransaction<K, V extends Serializable>
   private final V execute(final K key) throws TransactionException {
     // get from cache if possible...
     if (cacheIsEnabled() && key != null) {
-      final V cached = getCache().get(getKey());
+      final V cached = getCache().get(key);
       if (cached != null) {
         this.cached = true;
         return cached;
@@ -64,7 +64,7 @@ public abstract class CacheableBackendTransaction<K, V extends Serializable>
     if (notCached != null) {
       if (cacheIsEnabled() && key != null) {
         // now cache if possible
-        getCache().put(getKey(), notCached);
+        getCache().put(key, notCached);
       }
     }
     return notCached;
diff --git a/src/main/java/caosdb/server/database/DatabaseUtils.java b/src/main/java/caosdb/server/database/DatabaseUtils.java
index 84c38fa448b7d8a1035aa28ded7bbfcecc5a88b3..9edc0347abf8b63de0ce76ead06fb1f6883332e1 100644
--- a/src/main/java/caosdb/server/database/DatabaseUtils.java
+++ b/src/main/java/caosdb/server/database/DatabaseUtils.java
@@ -173,12 +173,7 @@ public class DatabaseUtils {
     while (rs.next()) {
       final FlatProperty fp = new FlatProperty();
       fp.id = rs.getInt("PropertyID");
-
-      final String v = bytes2UTF8(rs.getBytes("PropertyValue"));
-      if (v != null) {
-        fp.value = v;
-      }
-
+      fp.value = bytes2UTF8(rs.getBytes("PropertyValue"));
       fp.status = bytes2UTF8(rs.getBytes("PropertyStatus"));
       fp.idx = rs.getInt("PropertyIndex");
       ret.add(fp);
@@ -217,12 +212,14 @@ public class DatabaseUtils {
     ret.datatype = bytes2UTF8(rs.getBytes("Datatype"));
     ret.collection = bytes2UTF8(rs.getBytes("Collection"));
 
-    final String path = bytes2UTF8(rs.getBytes("FilePath"));
-    if (!rs.wasNull()) {
-      ret.filePath = path;
-      ret.fileSize = rs.getLong("FileSize");
-      ret.fileHash = bytes2UTF8(rs.getBytes("FileHash"));
-    }
+    ret.filePath = bytes2UTF8(rs.getBytes("FilePath"));
+    ret.fileSize = rs.getLong("FileSize");
+    ret.fileHash = bytes2UTF8(rs.getBytes("FileHash"));
+
+    ret.version = bytes2UTF8(rs.getBytes("Version"));
+    ret.versionSeconds = rs.getLong("VersionSeconds");
+    ret.versionNanos = rs.getInt("VersionNanos");
+
     return ret;
   }
 
diff --git a/src/main/java/caosdb/server/database/MySQLSetFileChecksum.java b/src/main/java/caosdb/server/database/MySQLSetFileChecksum.java
new file mode 100644
index 0000000000000000000000000000000000000000..a74843b65c12a49c05d77cb5df180b8b3317c3df
--- /dev/null
+++ b/src/main/java/caosdb/server/database/MySQLSetFileChecksum.java
@@ -0,0 +1,31 @@
+package caosdb.server.database;
+
+import caosdb.server.database.access.Access;
+import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
+import caosdb.server.database.backend.implementation.MySQL.MySQLTransaction;
+import caosdb.server.database.backend.interfaces.SetFileChecksumImpl;
+import caosdb.server.database.exceptions.TransactionException;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+public class MySQLSetFileChecksum extends MySQLTransaction implements SetFileChecksumImpl {
+
+  public MySQLSetFileChecksum(Access access) {
+    super(access);
+  }
+
+  public static final String STMT_SET_CHECKSUM =
+      "UPDATE files SET hash = unhex(?) WHERE file_id = ?";
+
+  @Override
+  public void execute(Integer id, String checksum) {
+    try {
+      PreparedStatement stmt = prepareStatement(STMT_SET_CHECKSUM);
+      stmt.setInt(2, id);
+      stmt.setString(1, checksum);
+      stmt.execute();
+    } catch (SQLException | ConnectionException e) {
+      throw new TransactionException(e);
+    }
+  }
+}
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
index 9700daf3a99b30e42baf8580f863998d7efe67a1..fc17dd1066cfa57660d44dbb6a521d3177e5fc72 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java
@@ -85,8 +85,7 @@ class DatabaseConnectionPool {
             + CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_PORT)
             + "/"
             + CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_DATABASE_NAME)
-            + "?noAccessToProcedureBodies=true&cacheCallableStmts=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8_unicode_ci&characterSetResults=utf8&serverTimezone=CET";
-    // + "?profileSQL=true&characterSetResults=utf8";
+            + "?noAccessToProcedureBodies=true&autoReconnect=true&serverTimezone=UTC&characterEncoding=UTF-8";
     final String user = CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_USER_NAME);
     final String pwd = CaosDBServer.getServerProperty(ServerProperties.KEY_MYSQL_USER_PASSWORD);
     final ConnectionPool pool = new ConnectionPool("MySQL Pool", 2, 5, 0, 0, url, user, pwd);
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
index 458823167423893b6defb166f013618be3a796d7..b443c54507e415ed4171fcf0ac99c3da762fb506 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLHelper.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,15 +24,20 @@
  */
 package caosdb.server.database.backend.implementation.MySQL;
 
+import caosdb.server.accessControl.Principal;
 import caosdb.server.database.misc.DBHelper;
+import caosdb.server.transaction.ChecksumUpdater;
 import caosdb.server.transaction.TransactionInterface;
 import caosdb.server.transaction.WriteTransaction;
-import caosdb.server.utils.Info;
-import caosdb.server.utils.Initialization;
+import java.io.UnsupportedEncodingException;
+import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.HashMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Provides cached statements for a MySQL back-end.
@@ -41,23 +48,71 @@ public class MySQLHelper implements DBHelper {
 
   private Connection connection = null;
 
+  private Logger logger = LoggerFactory.getLogger(getClass());
+
+  /**
+   * Initialize a transaction by calling the corresponding SQL procedure.
+   *
+   * <p>In the database, this adds a row to the transaction table with SRID, user and timestamp.
+   */
+  public void initTransaction(Connection connection, WriteTransaction<?> transaction)
+      throws SQLException {
+    try (CallableStatement call = connection.prepareCall("CALL set_transaction(?,?,?,?,?)")) {
+
+      String username = ((Principal) transaction.getTransactor().getPrincipal()).getUsername();
+      String realm = ((Principal) transaction.getTransactor().getPrincipal()).getRealm();
+      long seconds = transaction.getTimestamp().getUTCSeconds();
+      int nanos = transaction.getTimestamp().getNanoseconds();
+      byte[] srid = transaction.getSRID().getBytes("UTF-8");
+
+      call.setBytes(1, srid);
+      call.setString(2, username);
+      call.setString(3, realm);
+      call.setLong(4, seconds);
+      call.setInt(5, nanos);
+      call.execute();
+
+    } catch (UnsupportedEncodingException e) {
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  public Connection initConnection(TransactionInterface transaction)
+      throws ConnectionException, SQLException {
+    Connection connection;
+    connection = DatabaseConnectionPool.getConnection();
+
+    if (transaction instanceof ChecksumUpdater) {
+      connection.setReadOnly(false);
+      connection.setAutoCommit(false);
+    } else if (transaction instanceof WriteTransaction) {
+      connection.setReadOnly(false);
+      connection.setAutoCommit(false);
+      initTransaction(connection, (WriteTransaction<?>) transaction);
+    } else {
+      connection.setReadOnly(false);
+      connection.setAutoCommit(true);
+    }
+
+    return connection;
+  }
+
   public Connection getConnection() throws SQLException, ConnectionException {
     if (this.connection == null) {
-      this.connection = DatabaseConnectionPool.getConnection();
-      if (this.transaction instanceof WriteTransaction) {
-        this.connection.setReadOnly(false);
-        this.connection.setAutoCommit(false);
-      } else if (this.transaction instanceof Initialization || this.transaction instanceof Info) {
-        this.connection.setReadOnly(false);
-        this.connection.setAutoCommit(true);
-      } else {
-        this.connection.setReadOnly(false);
-        this.connection.setAutoCommit(true);
-      }
+      this.connection = initConnection(this.transaction);
     }
     return this.connection;
   }
 
+  /**
+   * Prepare a statement from a string. Reuse prepared statements from the cache if available.
+   *
+   * @param statement
+   * @return
+   * @throws SQLException
+   * @throws ConnectionException
+   */
   public PreparedStatement prepareStatement(final String statement)
       throws SQLException, ConnectionException {
     if (this.stmtCache.containsKey(statement)) {
@@ -81,6 +136,7 @@ public class MySQLHelper implements DBHelper {
 
   private TransactionInterface transaction = null;
 
+  /** Make all changes permanent. */
   @Override
   public void commit() throws SQLException {
     if (this.connection != null
@@ -90,13 +146,20 @@ public class MySQLHelper implements DBHelper {
     }
   }
 
+  /**
+   * Reset SRID variable, close all statements, roll back to last save point and close connection.
+   */
   @Override
   public void cleanUp() {
 
-    // close all statements (if necessary), roll back to last save point (if
-    // possible) and close connection (if necessary).
     try {
       if (this.connection != null && !this.connection.isClosed()) {
+        try (Statement s = connection.createStatement()) {
+          s.execute("SET @SRID = NULL");
+        } catch (SQLException e) {
+          logger.error("Exception while resetting the @SRID variable.", e);
+        }
+
         // close all cached statements (if possible)
         for (final PreparedStatement stmt : this.stmtCache.values()) {
           try {
@@ -104,7 +167,7 @@ public class MySQLHelper implements DBHelper {
               stmt.close();
             }
           } catch (final SQLException e) {
-            e.printStackTrace();
+            logger.warn("Exception while closing a prepared statement.", e);
           }
         }
 
@@ -113,12 +176,12 @@ public class MySQLHelper implements DBHelper {
             this.connection.rollback();
           }
         } catch (final SQLException r) {
-          r.printStackTrace();
+          logger.warn("Exception during roll-back attempt.", r);
         }
         this.connection.close();
       }
     } catch (final SQLException e) {
-      e.printStackTrace();
+      logger.warn("Exception during clean-up.", e);
     }
 
     // clear everything
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
index 4ad9f5b6a3cc3e51c05bc83bbc9332807b79484d..97cfde68b1b9097152aca3ea68705e6cdfddfaad 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java
@@ -22,6 +22,7 @@
  */
 package caosdb.server.database.backend.implementation.MySQL;
 
+import caosdb.server.database.DatabaseUtils;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.interfaces.InsertSparseEntityImpl;
 import caosdb.server.database.exceptions.IntegrityException;
@@ -56,6 +57,7 @@ public class MySQLInsertSparseEntity extends MySQLTransaction implements InsertS
       try (final ResultSet rs = insertEntityStmt.executeQuery()) {
         if (rs.next()) {
           entity.id = rs.getInt("EntityID");
+          entity.version = DatabaseUtils.bytes2UTF8(rs.getBytes("Version"));
         } else {
           throw new TransactionException("Didn't get new EntityID back.");
         }
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
index c1c430cfee6e7a6b88391aa02df072e918f8d692..721ba566ed389732456342aab78691c171b33922 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java
@@ -30,6 +30,7 @@ import caosdb.server.database.proto.VerySparseEntity;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Types;
 import java.util.ArrayList;
 
 public class MySQLRetrieveParents extends MySQLTransaction implements RetrieveParentsImpl {
@@ -38,16 +39,22 @@ public class MySQLRetrieveParents extends MySQLTransaction implements RetrievePa
     super(access);
   }
 
-  private static final String stmtStr = "call retrieveEntityParents(?)";
+  private static final String stmtStr = "call retrieveEntityParents(?, ?)";
 
   @Override
-  public ArrayList<VerySparseEntity> execute(final Integer id) throws TransactionException {
+  public ArrayList<VerySparseEntity> execute(final Integer id, final String version)
+      throws TransactionException {
     try {
       ResultSet rs = null;
       try {
         final PreparedStatement prepareStatement = prepareStatement(stmtStr);
 
         prepareStatement.setInt(1, id);
+        if (version == null) {
+          prepareStatement.setNull(2, Types.VARBINARY);
+        } else {
+          prepareStatement.setString(2, version);
+        }
         rs = prepareStatement.executeQuery();
         return DatabaseUtils.parseParentResultSet(rs);
       } finally {
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
index 8b9a547bac69cf221dbf7c204a93e95326bb484c..9b6704c4cb3e6395c5b8239a3aefc34bb818e937 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java
@@ -31,6 +31,7 @@ import caosdb.server.database.proto.ProtoProperty;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Types;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,15 +41,17 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
     super(access);
   }
 
-  private static final String stmtStr = "call retrieveEntityProperties(?,?)";
-  private static final String stmtStr2 = "call retrieveOverrides(?,?)";
+  private static final String stmtStr = "call retrieveEntityProperties(?,?,?)";
+  private static final String stmtStr2 = "call retrieveOverrides(?,?,?)";
 
   @Override
-  public ArrayList<ProtoProperty> execute(final Integer entity) throws TransactionException {
+  public ArrayList<ProtoProperty> execute(final Integer entity, final String version)
+      throws TransactionException {
     try {
       final PreparedStatement prepareStatement = prepareStatement(stmtStr);
 
-      final List<FlatProperty> props = retrieveFlatPropertiesStage1(0, entity, prepareStatement);
+      final List<FlatProperty> props =
+          retrieveFlatPropertiesStage1(0, entity, version, prepareStatement);
 
       final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>();
       for (final FlatProperty p : props) {
@@ -56,7 +59,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
         proto.property = p;
 
         final List<FlatProperty> subProps =
-            retrieveFlatPropertiesStage1(entity, p.id, prepareStatement);
+            retrieveFlatPropertiesStage1(entity, p.id, version, prepareStatement);
 
         proto.subProperties = subProps;
 
@@ -71,7 +74,10 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
   }
 
   private List<FlatProperty> retrieveFlatPropertiesStage1(
-      final Integer domain, final Integer entity, final PreparedStatement stmt)
+      final Integer domain,
+      final Integer entity,
+      final String version,
+      final PreparedStatement stmt)
       throws SQLException, ConnectionException {
     ResultSet rs = null;
     try {
@@ -82,6 +88,12 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
       }
 
       stmt.setInt(2, entity);
+      if (version == null) {
+        stmt.setNull(3, Types.VARBINARY);
+      } else {
+        stmt.setString(3, version);
+      }
+
       long t1 = System.currentTimeMillis();
       rs = stmt.executeQuery();
       long t2 = System.currentTimeMillis();
@@ -91,7 +103,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
 
       final PreparedStatement stmt2 = prepareStatement(stmtStr2);
 
-      retrieveOverrides(domain, entity, stmt2, props);
+      retrieveOverrides(domain, entity, version, stmt2, props);
 
       return props;
     } finally {
@@ -104,6 +116,7 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
   private void retrieveOverrides(
       final Integer domain,
       final Integer entity,
+      final String version,
       final PreparedStatement stmt2,
       final List<FlatProperty> props)
       throws SQLException {
@@ -112,6 +125,11 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev
     try {
       stmt2.setInt(1, domain);
       stmt2.setInt(2, entity);
+      if (version == null) {
+        stmt2.setNull(3, Types.VARBINARY);
+      } else {
+        stmt2.setString(3, version);
+      }
       long t1 = System.currentTimeMillis();
       rs = stmt2.executeQuery();
       long t2 = System.currentTimeMillis();
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java
index 92f2cd5fe4c26d796112baec5d908b9431187ae7..c6d88ec9f8fca1651318456161ed2c0347a47f21 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java
@@ -28,6 +28,7 @@ import caosdb.server.database.exceptions.TransactionException;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Types;
 
 public class MySQLRetrieveQueryTemplateDefinition extends MySQLTransaction
     implements RetrieveQueryTemplateDefinitionImpl {
@@ -37,14 +38,19 @@ public class MySQLRetrieveQueryTemplateDefinition extends MySQLTransaction
   }
 
   public static final String STMT_RETRIEVE_QUERY_TEMPLATE_DEF =
-      "SELECT definition FROM query_template_def WHERE id=?";
+      "call retrieveQueryTemplateDef(?,?)";
 
   @Override
-  public String retrieve(final Integer id) {
+  public String retrieve(final Integer id, final String version) {
     try {
 
       final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_QUERY_TEMPLATE_DEF);
       stmt.setInt(1, id);
+      if (version == null) {
+        stmt.setNull(2, Types.VARBINARY);
+      } else {
+        stmt.setString(2, version);
+      }
       final ResultSet rs = stmt.executeQuery();
       if (rs.next()) {
         return rs.getString("definition");
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
index 2aff2aaedda70672eb676597a2e3ba7a1db14352..097961974b0c599480e8186f170665838e7d046e 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java
@@ -30,6 +30,7 @@ import caosdb.server.database.proto.SparseEntity;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Types;
 
 /**
  * Retrieve a single SparseEntity by id.
@@ -43,14 +44,19 @@ public class MySQLRetrieveSparseEntity extends MySQLTransaction
     super(access);
   }
 
-  private static final String stmtStr = "call retrieveEntity(?)";
+  private static final String stmtStr = "call retrieveEntity(?,?)";
 
   @Override
-  public SparseEntity execute(final int id) throws TransactionException {
+  public SparseEntity execute(final int id, final String version) throws TransactionException {
     try {
       final PreparedStatement preparedStatement = prepareStatement(stmtStr);
 
       preparedStatement.setInt(1, id);
+      if (version == null) {
+        preparedStatement.setNull(2, Types.VARBINARY);
+      } else {
+        preparedStatement.setString(2, version);
+      }
       try (final ResultSet rs = preparedStatement.executeQuery()) {
         if (rs.next()) {
           return DatabaseUtils.parseEntityResultSet(rs);
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..22ac0c2e30c0f48fbc884f40593bac2dc45d7b7d
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java
@@ -0,0 +1,85 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.database.backend.implementation.MySQL;
+
+import caosdb.server.database.DatabaseUtils;
+import caosdb.server.database.access.Access;
+import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
+import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.proto.VersionHistoryItem;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Transaction to retrieve all versions of an entity.
+ *
+ * <p>Creates a mapping ID :: (VersionHistoryItem)
+ */
+public class MySQLRetrieveVersionHistory extends MySQLTransaction
+    implements RetrieveVersionHistoryImpl {
+
+  public static final String VERSION_HISTORY_STMT = "CALL get_version_history(?)";
+
+  public MySQLRetrieveVersionHistory(Access access) {
+    super(access);
+  }
+
+  @Override
+  public HashMap<String, VersionHistoryItem> execute(Integer entityId) {
+
+    HashMap<String, VersionHistoryItem> result = new HashMap<>();
+    try {
+      PreparedStatement s = prepareStatement(VERSION_HISTORY_STMT);
+      s.setInt(1, entityId);
+      ResultSet rs = s.executeQuery();
+
+      while (rs.next()) {
+        String childId = DatabaseUtils.bytes2UTF8(rs.getBytes("child"));
+        String parentId = DatabaseUtils.bytes2UTF8(rs.getBytes("parent"));
+        Long childSeconds = rs.getLong("child_seconds");
+        Integer childNanos = rs.getInt("child_nanos");
+        VersionHistoryItem v = result.get(childId);
+        if (v == null) {
+          v = new VersionHistoryItem();
+          v.id = childId;
+          v.seconds = childSeconds;
+          v.nanos = childNanos;
+          result.put(childId, v);
+        }
+
+        if (parentId != null) {
+          if (v.parents == null) {
+            v.parents = new LinkedList<>();
+          }
+          v.parents.add(parentId);
+        }
+      }
+    } catch (SQLException | ConnectionException e) {
+      throw new TransactionException(e);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
index 4cc59f9d0f0a813bb92294f6fabb1cd51f43c151..73c589fade317586cc1b5bfb4b796578f9e819ba 100644
--- a/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java
@@ -22,12 +22,14 @@
  */
 package caosdb.server.database.backend.implementation.MySQL;
 
+import caosdb.server.database.DatabaseUtils;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl;
 import caosdb.server.database.exceptions.IntegrityException;
 import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.database.proto.SparseEntity;
 import java.sql.PreparedStatement;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLIntegrityConstraintViolationException;
 import java.sql.Types;
@@ -39,15 +41,31 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS
   }
 
   public static final String STMT_UPDATE_ENTITY = "call updateEntity(?,?,?,?,?,?,?)";
-  public static final String STMT_UPDATE_FILE_PROPS =
-      "INSERT INTO files (hash, size, path, file_id) VALUES (unhex(?),?,?,?) ON DUPLICATE KEY UPDATE hash=unhex(?), size=?, path=?;";
+  public static final String STMT_UPDATE_FILE_PROPS = "call setFileProperties(?,?,?,?)";
 
   @Override
   public void execute(final SparseEntity spe) throws TransactionException {
     try {
-      final PreparedStatement updateEntityStmt = prepareStatement(STMT_UPDATE_ENTITY);
+      // file properties;
+      final PreparedStatement updateFilePropsStmt = prepareStatement(STMT_UPDATE_FILE_PROPS);
+      updateFilePropsStmt.setInt(1, spe.id);
+      if (spe.filePath != null) {
+        updateFilePropsStmt.setString(2, spe.filePath);
+        updateFilePropsStmt.setLong(3, spe.fileSize);
+        if (spe.fileHash != null) {
+          updateFilePropsStmt.setString(4, spe.fileHash);
+        } else {
+          updateFilePropsStmt.setNull(4, Types.VARCHAR);
+        }
+      } else {
+        updateFilePropsStmt.setNull(2, Types.VARCHAR);
+        updateFilePropsStmt.setNull(3, Types.BIGINT);
+        updateFilePropsStmt.setNull(4, Types.VARCHAR);
+      }
+      updateFilePropsStmt.execute();
 
       // very sparse entity
+      final PreparedStatement updateEntityStmt = prepareStatement(STMT_UPDATE_ENTITY);
       updateEntityStmt.setInt(1, spe.id);
       updateEntityStmt.setString(2, spe.name);
       updateEntityStmt.setString(3, spe.description);
@@ -59,28 +77,12 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS
       }
       updateEntityStmt.setString(6, spe.collection);
       updateEntityStmt.setString(7, spe.acl);
-      updateEntityStmt.execute();
+      ResultSet rs = updateEntityStmt.executeQuery();
 
-      // file properties;
-      if (spe.filePath != null) {
-        final PreparedStatement updateFilePropsStmt = prepareStatement(STMT_UPDATE_FILE_PROPS);
-        if (spe.fileHash != null) {
-          updateFilePropsStmt.setString(1, spe.fileHash);
-          updateFilePropsStmt.setString(5, spe.fileHash);
-        } else {
-          updateFilePropsStmt.setNull(1, Types.VARCHAR);
-          updateFilePropsStmt.setNull(5, Types.VARCHAR);
-        }
-        updateFilePropsStmt.setLong(2, spe.fileSize);
-        updateFilePropsStmt.setLong(6, spe.fileSize);
-
-        updateFilePropsStmt.setString(3, spe.filePath);
-        updateFilePropsStmt.setString(7, spe.filePath);
-
-        updateFilePropsStmt.setInt(4, spe.id);
-
-        updateFilePropsStmt.execute();
+      if (rs.next()) {
+        spe.version = DatabaseUtils.bytes2UTF8(rs.getBytes("Version"));
       }
+
     } catch (final SQLIntegrityConstraintViolationException e) {
       throw new IntegrityException(e);
     } catch (final SQLException e) {
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
index 551ee962d8768aa90b9f5aaed313c050f0873079..9dfb8cf57c260c7fe154c25f095886ca6a3c2698 100644
--- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
+++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java
@@ -28,5 +28,6 @@ import java.util.ArrayList;
 
 public interface RetrieveParentsImpl extends BackendTransactionImpl {
 
-  public ArrayList<VerySparseEntity> execute(Integer id) throws TransactionException;
+  public ArrayList<VerySparseEntity> execute(Integer id, String version)
+      throws TransactionException;
 }
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
index 3c8449f7fc62581457091ea5ab0ccc5921b89a92..b9a15a526b857b9674432a96733c6f2fd8e5d4ea 100644
--- a/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
+++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java
@@ -28,5 +28,5 @@ import java.util.ArrayList;
 
 public interface RetrievePropertiesImpl extends BackendTransactionImpl {
 
-  public ArrayList<ProtoProperty> execute(Integer id) throws TransactionException;
+  public ArrayList<ProtoProperty> execute(Integer id, String version) throws TransactionException;
 }
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java
index fee860ca2c0ee4341ae91a4a1736d518cd71a5e8..cda336044de4bd0d37a3d67da140c744f2699f32 100644
--- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java
+++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveQueryTemplateDefinitionImpl.java
@@ -24,5 +24,5 @@ package caosdb.server.database.backend.interfaces;
 
 public interface RetrieveQueryTemplateDefinitionImpl extends BackendTransactionImpl {
 
-  public String retrieve(final Integer id);
+  public String retrieve(Integer id, String version);
 }
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java
index 82a534c03182d577e7b27eca9b2ff9def1c60677..5b6978f8f456bf978c3ca60ae0da479eaf54ed0a 100644
--- a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java
+++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveSparseEntityImpl.java
@@ -27,5 +27,5 @@ import caosdb.server.database.proto.SparseEntity;
 
 public interface RetrieveSparseEntityImpl extends BackendTransactionImpl {
 
-  public SparseEntity execute(int id) throws TransactionException;
+  public SparseEntity execute(int id, String version) throws TransactionException;
 }
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..118e783d816ce45d1a43738bf8959e4a94740d3c
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/interfaces/RetrieveVersionHistoryImpl.java
@@ -0,0 +1,9 @@
+package caosdb.server.database.backend.interfaces;
+
+import caosdb.server.database.proto.VersionHistoryItem;
+import java.util.HashMap;
+
+public interface RetrieveVersionHistoryImpl extends BackendTransactionImpl {
+
+  public HashMap<String, VersionHistoryItem> execute(Integer entityId);
+}
diff --git a/src/main/java/caosdb/server/database/backend/interfaces/SetFileChecksumImpl.java b/src/main/java/caosdb/server/database/backend/interfaces/SetFileChecksumImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..be73b33ecfa646dc7f19b2db3d38a8a464065f09
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/interfaces/SetFileChecksumImpl.java
@@ -0,0 +1,6 @@
+package caosdb.server.database.backend.interfaces;
+
+public interface SetFileChecksumImpl extends BackendTransactionImpl {
+
+  void execute(Integer id, String checksum);
+}
diff --git a/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java b/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java
index 15ace2b9820398cb270a6a1e098e6f642b66ab9d..984ca5eb853e356814aafb69ebd9860bee4fe8a1 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/DeleteEntityProperties.java
@@ -40,8 +40,8 @@ public class DeleteEntityProperties extends BackendTransaction {
 
   @Override
   public void execute() {
-    RetrieveProperties.removeCached(this.entity.getId());
-    RetrieveParents.removeCached(this.entity.getId());
+    RetrieveProperties.removeCached(this.entity.getIdVersion());
+    RetrieveParents.removeCached(this.entity.getIdVersion());
 
     final DeleteEntityPropertiesImpl ret = getImplementation(DeleteEntityPropertiesImpl.class);
 
diff --git a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java
index 39deb1416c743f838bfc1b96c29fd845dbcef742..a2e4de6482d78053ab01c70ea3634c3f8af232a3 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/DeleteSparseEntity.java
@@ -42,7 +42,7 @@ public class DeleteSparseEntity extends BackendTransaction {
 
   @Override
   protected void execute() {
-    RetrieveSparseEntity.removeCached(this.entity.getId());
+    RetrieveSparseEntity.removeCached(this.entity);
     if (entity.hasFileProperties()) {
       GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath());
     }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java
index f8711197064a88dd92c408f329d1a36df55c5ed2..c8e20895711e16c40c75c89c9776e8ad55851117 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/InsertSparseEntity.java
@@ -30,6 +30,7 @@ import caosdb.server.database.exceptions.IntegrityException;
 import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.database.proto.SparseEntity;
 import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.Version;
 import caosdb.server.utils.Undoable;
 
 public class InsertSparseEntity extends BackendTransaction {
@@ -68,5 +69,6 @@ public class InsertSparseEntity extends BackendTransaction {
               public void cleanUp() {}
             });
     this.entity.setId(e.id);
+    this.entity.setVersion(new Version(e.version));
   }
 }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java
index 7ac8c1592e0cb275e60008532cdabb33919369a9..ad0d39e94b56904def08a04a8a42a89627a7b4d6 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveFullEntity.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,12 +25,32 @@
 package caosdb.server.database.backend.transaction;
 
 import caosdb.server.database.BackendTransaction;
+import caosdb.server.datatype.ReferenceDatatype;
+import caosdb.server.datatype.ReferenceValue;
 import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.Message;
 import caosdb.server.entity.RetrieveEntity;
 import caosdb.server.entity.Role;
 import caosdb.server.entity.container.Container;
+import caosdb.server.entity.wrapper.Property;
+import caosdb.server.query.Query;
+import caosdb.server.query.Query.Selection;
 import caosdb.server.utils.EntityStatus;
+import java.util.LinkedList;
+import java.util.List;
 
+/**
+ * Retrieve the full entity from the backend - with all parents, properties, file properties and so
+ * on.
+ *
+ * <p>TODO: This class should rather be called FullEntityRetrieval or FullEntityRetrieveTransaction.
+ *
+ * <p>When the entity which is to be retrieved has a defined list of {@link Query.Selection} which
+ * select properties from referenced entities, the referenced entities are retrieved as well.
+ * Otherwise, only the referenced id is retrieved and the entity stays rather flat.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
 public class RetrieveFullEntity extends BackendTransaction {
 
   private final Container<? extends EntityInterface> container;
@@ -49,22 +71,107 @@ public class RetrieveFullEntity extends BackendTransaction {
 
   @Override
   public void execute() {
-    for (final EntityInterface e : this.container) {
+    retrieveFullEntitiesInContainer(this.container);
+  }
+
+  /**
+   * Retrieve the entities in the container.
+   *
+   * @param container
+   */
+  public void retrieveFullEntitiesInContainer(Container<? extends EntityInterface> container) {
+    for (final EntityInterface e : container) {
       if (e.hasId() && e.getId() > 0 && e.getEntityStatus() == EntityStatus.QUALIFIED) {
+        retrieveFullEntity(e, e.getSelections());
+      }
+    }
+  }
 
-        execute(new RetrieveSparseEntity(e));
-        if (e.getEntityStatus() == EntityStatus.VALID) {
-          if (e.getRole() == Role.QueryTemplate) {
-            execute(new RetrieveQueryTemplateDefinition(e));
-          }
-          execute(new RetrieveParents(e));
+  /**
+   * Retrieve a single full entity.
+   *
+   * <p>If the selections are not empty, retrieve the referenced entities matching the 'selections'
+   * as well.
+   *
+   * <p>This method is called recursively during the retrieval of the referenced entities.
+   *
+   * @param e The entity.
+   * @param selections
+   */
+  public void retrieveFullEntity(EntityInterface e, List<Selection> selections) {
+    execute(new RetrieveSparseEntity(e));
+
+    if (e.getEntityStatus() == EntityStatus.VALID) {
+      if (e.getRole() == Role.QueryTemplate) {
+        execute(new RetrieveQueryTemplateDefinition(e));
+      }
+      execute(new RetrieveParents(e));
+      execute(new RetrieveProperties(e));
+      execute(new RetrieveVersionInfo(e));
 
-          execute(new RetrieveProperties(e));
+      // recursion! retrieveSubEntities calls retrieveFull sometimes, but with reduced selectors.
+      if (selections != null && !selections.isEmpty()) {
+        retrieveSubEntities(e, selections);
+      }
+    }
+  }
+
+  /**
+   * Retrieve the Entities which match the selections and are referenced by the Entity 'e'.
+   *
+   * @param e
+   * @param selections
+   */
+  public void retrieveSubEntities(EntityInterface e, List<Selection> selections) {
+    for (final Selection s : selections) {
+      if (s.getSubselection() != null) {
+
+        String propertyName = s.getSelector();
+
+        // Find matching (i.e. referencing) Properties
+        for (Property p : e.getProperties()) {
+          // get reference properties by name.
+          if (propertyName.equalsIgnoreCase(p.getName())
+              && p.getDatatype() instanceof ReferenceDatatype) {
+            if (p.getValue() != null) {
+              try {
+                p.parseValue();
+              } catch (Message m) {
+                p.addError(m);
+              }
+
+              ReferenceValue value = (ReferenceValue) p.getValue();
+              RetrieveEntity ref = new RetrieveEntity(value.getId());
+              // recursion!  (Only for the matching selections)
+              retrieveFullEntity(ref, getSubSelects(selections, propertyName));
+              value.setEntity(ref, true);
+            }
+          }
         }
       }
     }
   }
 
+  /**
+   * Return all non-null subselects of those selections which match the given select String.
+   *
+   * <p>Effectively, this reduces the depth of the selections by one (and drops non-matching
+   * selections).
+   *
+   * @param selections
+   * @param select
+   * @return A new list of Selections.
+   */
+  public List<Selection> getSubSelects(List<Selection> selections, String select) {
+    List<Selection> result = new LinkedList<>();
+    for (Selection s : selections) {
+      if (s.getSelector().equalsIgnoreCase(select) && s.getSubselection() != null) {
+        result.add(s.getSubselection());
+      }
+    }
+    return result;
+  }
+
   public Container<? extends EntityInterface> getContainer() {
     return container;
   }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java
index 5c1b0178f51496f83a4fac6a4ce3b2447abdccd5..d33d5e96d978fe86d6eb965113726eb79bb60d40 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveParents.java
@@ -34,10 +34,22 @@ import caosdb.server.entity.EntityInterface;
 import java.util.ArrayList;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 
+// TODO Problem with the caching.
+// When an old entity version has a parent which is deleted, the name is
+// still in the cached VerySparseEntity. This can be resolved by using a
+// similar strategy as in RetrieveProperties.java where the name etc. are
+// retrieved in a second step. Thus the deletion doesn't slip through
+// unnoticed.
+//
+// Changes are necessary in the backend-api, i.e. mysqlbackend and the
+// interfaces as well.
+//
+// See also a failing test in caosdb-pyinttest:
+// tests/test_version.py::test_bug_cached_parent_name_in_old_version
 public class RetrieveParents
-    extends CacheableBackendTransaction<Integer, ArrayList<VerySparseEntity>> {
+    extends CacheableBackendTransaction<String, ArrayList<VerySparseEntity>> {
 
-  private static final ICacheAccess<Integer, ArrayList<VerySparseEntity>> cache =
+  private static final ICacheAccess<String, ArrayList<VerySparseEntity>> cache =
       Cache.getCache("BACKEND_EntityParents");
 
   /**
@@ -45,9 +57,9 @@ public class RetrieveParents
    *
    * @param id
    */
-  public static void removeCached(final Integer id) {
-    if (id != null && cache != null) {
-      cache.remove(id);
+  public static void removeCached(final String idVersion) {
+    if (idVersion != null && cache != null) {
+      cache.remove(idVersion);
     }
   }
 
@@ -61,18 +73,18 @@ public class RetrieveParents
   @Override
   public ArrayList<VerySparseEntity> executeNoCache() throws TransactionException {
     final RetrieveParentsImpl t = getImplementation(RetrieveParentsImpl.class);
-    final Integer key = getKey();
-    return t.execute(key);
+    return t.execute(this.entity.getId(), this.entity.getVersion().getId());
   }
 
   @Override
   protected void process(final ArrayList<VerySparseEntity> t) throws TransactionException {
     this.entity.getParents().clear();
+
     DatabaseUtils.parseParentsFromVerySparseEntity(this.entity, t);
   }
 
   @Override
-  protected Integer getKey() {
-    return this.entity.getId();
+  protected String getKey() {
+    return this.entity.getIdVersion();
   }
 }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java
index 487feb56cf848e948fcacb8f33f49e4a0bc2267d..822f57fd49e54d7517578592acf09100924fe81b 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveProperties.java
@@ -37,11 +37,11 @@ import java.util.ArrayList;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 
 public class RetrieveProperties
-    extends CacheableBackendTransaction<Integer, ArrayList<ProtoProperty>> {
+    extends CacheableBackendTransaction<String, ArrayList<ProtoProperty>> {
 
   private final EntityInterface entity;
   public static final String CACHE_REGION = "BACKEND_EntityProperties";
-  private static final ICacheAccess<Integer, ArrayList<ProtoProperty>> cache =
+  private static final ICacheAccess<String, ArrayList<ProtoProperty>> cache =
       Cache.getCache(CACHE_REGION);
 
   /**
@@ -49,9 +49,9 @@ public class RetrieveProperties
    *
    * @param id
    */
-  protected static void removeCached(final Integer id) {
-    if (id != null && cache != null) {
-      cache.remove(id);
+  protected static void removeCached(final String idVersion) {
+    if (idVersion != null && cache != null) {
+      cache.remove(idVersion);
     }
   }
 
@@ -63,7 +63,7 @@ public class RetrieveProperties
   @Override
   public ArrayList<ProtoProperty> executeNoCache() throws TransactionException {
     final RetrievePropertiesImpl t = getImplementation(RetrievePropertiesImpl.class);
-    return t.execute(getKey());
+    return t.execute(this.entity.getId(), this.entity.getVersion().getId());
   }
 
   @Override
@@ -98,7 +98,7 @@ public class RetrieveProperties
   }
 
   @Override
-  protected Integer getKey() {
-    return this.entity.getId();
+  protected String getKey() {
+    return this.entity.getIdVersion();
   }
 }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java
index 208c7439aa6cf9ff8f32abc8f5cd59ea9a3cba95..0c96c72cb0be41aaf368c22009526914994bc8ec 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveQueryTemplateDefinition.java
@@ -39,6 +39,7 @@ public class RetrieveQueryTemplateDefinition extends BackendTransaction {
   protected void execute() throws TransactionException {
     final RetrieveQueryTemplateDefinitionImpl t =
         getImplementation(RetrieveQueryTemplateDefinitionImpl.class);
-    this.entity.setQueryTemplateDefinition(t.retrieve(this.entity.getId()));
+    this.entity.setQueryTemplateDefinition(
+        t.retrieve(this.entity.getId(), this.entity.getVersion().getId()));
   }
 }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
index a91b5021f1fa722a8e8831234fc8ebc2032ba115..7843f57957d392c4bbf65f01a60d559cff11c6c8 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java
@@ -3,9 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- * Copyright (C) 2019 IndiScale GmbH
- * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com)
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2019,2020 IndiScale GmbH
+ * Copyright (C) 2019,2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -35,20 +35,21 @@ import caosdb.server.entity.EntityInterface;
 import caosdb.server.utils.EntityStatus;
 import org.apache.commons.jcs.access.behavior.ICacheAccess;
 
-public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, SparseEntity> {
+public class RetrieveSparseEntity extends CacheableBackendTransaction<String, SparseEntity> {
 
   private final EntityInterface entity;
-  private static final ICacheAccess<Integer, SparseEntity> cache =
+  private static final ICacheAccess<String, SparseEntity> cache =
       Cache.getCache("BACKEND_SparseEntities");
 
   /**
-   * To be called by {@link UpdateSparseEntity} and {@link DeleteEntity} on execution.
+   * To be called by {@link UpdateSparseEntity} and {@link DeleteSparseEntity} on execution.
    *
-   * @param id
+   * @param entity
    */
-  public static void removeCached(final Integer id) {
-    if (id != null && cache != null) {
-      cache.remove(id);
+  public static void removeCached(final EntityInterface entity) {
+    if (entity != null && cache != null) {
+      cache.remove(entity.getId().toString());
+      cache.remove(entity.getIdVersion());
     }
   }
 
@@ -57,14 +58,15 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, S
     this.entity = entity;
   }
 
-  public RetrieveSparseEntity(final int id) {
+  public RetrieveSparseEntity(final int id, final String version) {
     this(new Entity(id));
+    this.entity.getVersion().setId(version);
   }
 
   @Override
   public SparseEntity executeNoCache() throws TransactionException {
     final RetrieveSparseEntityImpl t = getImplementation(RetrieveSparseEntityImpl.class);
-    final SparseEntity ret = t.execute(getKey());
+    final SparseEntity ret = t.execute(getEntity().getId(), getEntity().getVersion().getId());
     if (ret == null) {
       this.entity.setEntityStatus(EntityStatus.NONEXISTENT);
     }
@@ -78,8 +80,14 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<Integer, S
   }
 
   @Override
-  protected Integer getKey() {
-    return this.entity.getId();
+  protected String getKey() {
+    if ("HEAD".equalsIgnoreCase(entity.getVersion().getId())) {
+      return this.entity.getId().toString();
+    } else if (entity.hasVersion()
+        && entity.getVersion().getId().toUpperCase().startsWith("HEAD")) {
+      return null;
+    }
+    return this.entity.getIdVersion();
   }
 
   public EntityInterface getEntity() {
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..20be2f8fb60ce54877fdb2fd85d27a6944a71d86
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionHistory.java
@@ -0,0 +1,79 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.database.backend.transaction;
+
+import caosdb.server.database.CacheableBackendTransaction;
+import caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl;
+import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.proto.VersionHistoryItem;
+import caosdb.server.entity.EntityInterface;
+import java.util.Collection;
+import java.util.HashMap;
+
+public abstract class RetrieveVersionHistory
+    extends CacheableBackendTransaction<Integer, HashMap<String, VersionHistoryItem>> {
+
+  // TODO
+  // private static final ICacheAccess<String, Version> cache =
+  // Cache.getCache("BACKEND_RetrieveVersionHistory");
+  private EntityInterface entity;
+  private HashMap<String, VersionHistoryItem> map;
+
+  public static void removeCached(Integer entityId) {
+    // TODO
+  }
+
+  public RetrieveVersionHistory(EntityInterface e) {
+    super(null); // TODO caching
+    this.entity = e;
+  }
+
+  @Override
+  public HashMap<String, VersionHistoryItem> executeNoCache() throws TransactionException {
+    RetrieveVersionHistoryImpl impl = getImplementation(RetrieveVersionHistoryImpl.class);
+    return impl.execute(getKey());
+  }
+
+  /** After this method call, the version map is available to the object. */
+  @Override
+  protected void process(HashMap<String, VersionHistoryItem> map) throws TransactionException {
+    this.map = map;
+  }
+
+  @Override
+  protected Integer getKey() {
+    return entity.getId();
+  }
+
+  public HashMap<String, VersionHistoryItem> getMap() {
+    return this.map;
+  }
+
+  public EntityInterface getEntity() {
+    return this.entity;
+  }
+
+  public Collection<VersionHistoryItem> getList() {
+    return this.map.values();
+  }
+}
diff --git a/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..1daab0504cafbd05e9e03dff78b2c230a0438ccd
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/transaction/RetrieveVersionInfo.java
@@ -0,0 +1,84 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.database.backend.transaction;
+
+import caosdb.datetime.UTCDateTime;
+import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.proto.VersionHistoryItem;
+import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.Version;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+public class RetrieveVersionInfo extends RetrieveVersionHistory {
+
+  public RetrieveVersionInfo(EntityInterface e) {
+    super(e);
+  }
+
+  @Override
+  protected void process(HashMap<String, VersionHistoryItem> map) throws TransactionException {
+    super.process(map); // Make the map available to the object.
+    if (!map.isEmpty()) getVersion();
+  }
+
+  public Version getVersion() {
+    Version v = getEntity().getVersion();
+    VersionHistoryItem i = getMap().get(v.getId());
+    if (i != null) v.setDate(UTCDateTime.UTCSeconds(i.seconds, i.nanos));
+
+    v.setPredecessors(getPredecessors(v.getId()));
+    v.setSuccessors(getSuccessors(v.getId()));
+    return v;
+  }
+
+  /** Return a list of direct children. */
+  private LinkedList<Version> getSuccessors(String id) {
+    LinkedList<Version> result = new LinkedList<>();
+
+    outer:
+    for (VersionHistoryItem i : getList()) {
+      if (i.parents != null)
+        for (String p : i.parents) {
+          if (id.equals(p)) {
+            Version successor = new Version(i.id, i.seconds, i.nanos);
+            result.add(successor);
+            continue outer;
+          }
+        }
+    }
+    return result;
+  }
+
+  /** Return a list of direct parents. */
+  private LinkedList<Version> getPredecessors(String id) {
+    LinkedList<Version> result = new LinkedList<>();
+    if (getMap().containsKey(id) && getMap().get(id).parents != null)
+      for (String p : getMap().get(id).parents) {
+        VersionHistoryItem i = getMap().get(p);
+        Version predecessor = new Version(i.id, i.seconds, i.nanos);
+        result.add(predecessor);
+      }
+    return result;
+  }
+}
diff --git a/src/main/java/caosdb/server/database/backend/transaction/SetFileChecksum.java b/src/main/java/caosdb/server/database/backend/transaction/SetFileChecksum.java
new file mode 100644
index 0000000000000000000000000000000000000000..b19bceabef39d48cdd778835f3d2840bbec29297
--- /dev/null
+++ b/src/main/java/caosdb/server/database/backend/transaction/SetFileChecksum.java
@@ -0,0 +1,26 @@
+package caosdb.server.database.backend.transaction;
+
+import caosdb.server.database.BackendTransaction;
+import caosdb.server.database.backend.interfaces.SetFileChecksumImpl;
+import caosdb.server.entity.EntityInterface;
+
+public class SetFileChecksum extends BackendTransaction {
+
+  private EntityInterface entity;
+
+  public SetFileChecksum(EntityInterface entity) {
+    this.entity = entity;
+  }
+
+  @Override
+  protected void execute() {
+    RetrieveSparseEntity.removeCached(this.entity);
+    if (entity.hasFileProperties()) {
+      GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath());
+
+      final SetFileChecksumImpl t = getImplementation(SetFileChecksumImpl.class);
+
+      t.execute(this.entity.getId(), this.entity.getFileProperties().getChecksum());
+    }
+  }
+}
diff --git a/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java b/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java
index 6378e8bbbbd8aee87382d226df0691d004e641aa..a7c9ba43f32b1ef97a6a09544041b39a2cd5c71a 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/UpdateEntity.java
@@ -53,6 +53,8 @@ public class UpdateEntity extends BackendTransaction {
         execute(new InsertEntityValue(e));
 
         execute(new InsertEntityProperties(e));
+
+        execute(new RetrieveVersionInfo(e));
       }
     }
   }
diff --git a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java
index c6a1c7683409e0c875484e14553e4aeac2757a71..309ff9cbde0131f4b90cc89301a2338a8b92971f 100644
--- a/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java
+++ b/src/main/java/caosdb/server/database/backend/transaction/UpdateSparseEntity.java
@@ -29,6 +29,7 @@ import caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl;
 import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.database.proto.SparseEntity;
 import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.Version;
 
 public class UpdateSparseEntity extends BackendTransaction {
 
@@ -40,7 +41,7 @@ public class UpdateSparseEntity extends BackendTransaction {
 
   @Override
   public void execute() throws TransactionException {
-    RetrieveSparseEntity.removeCached(this.entity.getId());
+    RetrieveSparseEntity.removeCached(this.entity);
     if (entity.hasFileProperties()) {
       GetFileRecordByPath.removeCached(this.entity.getFileProperties().getPath());
     }
@@ -50,5 +51,7 @@ public class UpdateSparseEntity extends BackendTransaction {
     final SparseEntity spe = this.entity.getSparseEntity();
 
     t.execute(spe);
+
+    this.entity.setVersion(new Version(spe.version));
   }
 }
diff --git a/src/main/java/caosdb/server/database/proto/SparseEntity.java b/src/main/java/caosdb/server/database/proto/SparseEntity.java
index 4f5dcc69ce5ed7c6afa636b97237b53abb56ab51..d70d4ae026eb72d8c782b763ae3fc21d03a73064 100644
--- a/src/main/java/caosdb/server/database/proto/SparseEntity.java
+++ b/src/main/java/caosdb/server/database/proto/SparseEntity.java
@@ -38,6 +38,9 @@ public class SparseEntity extends VerySparseEntity {
   public String filePath = null;
   public Long fileSize = null;
   public Long fileChecked = null;
+  public String version = null;
+  public Long versionSeconds = null;
+  public Integer versionNanos = null;
 
   @Override
   public String toString() {
@@ -51,6 +54,9 @@ public class SparseEntity extends VerySparseEntity {
         .append(this.fileHash)
         .append(this.filePath)
         .append(this.fileSize)
+        .append(this.version)
+        .append(this.versionSeconds)
+        .append(this.versionNanos)
         .toString();
   }
 }
diff --git a/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java b/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..e671950f39f40c0b93069d031f636ad964560508
--- /dev/null
+++ b/src/main/java/caosdb/server/database/proto/VersionHistoryItem.java
@@ -0,0 +1,13 @@
+package caosdb.server.database.proto;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+
+public class VersionHistoryItem implements Serializable {
+
+  private static final long serialVersionUID = 7855704308135158698L;
+  public String id = null;
+  public LinkedList<String> parents = null;
+  public Long seconds = null;
+  public Integer nanos = null;
+}
diff --git a/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java b/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java
index ce92aa022cfe85573049eaaeed4e26653b400679..87c17079b0facdc193ea79dd6005598f7fe151b6 100644
--- a/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java
+++ b/src/main/java/caosdb/server/datatype/ReferenceDatatype2.java
@@ -47,7 +47,7 @@ public class ReferenceDatatype2 extends ReferenceDatatype {
   }
 
   public void setEntity(final EntityInterface datatypeEntity) {
-    this.refid.setEntity(datatypeEntity);
+    this.refid.setEntity(datatypeEntity, false);
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/datatype/ReferenceValue.java b/src/main/java/caosdb/server/datatype/ReferenceValue.java
index 25fa0b381cb436236dd6e3676970cf7e54c17776..77f7aa8c56d97b8dc5abf914c0b6c88749683d69 100644
--- a/src/main/java/caosdb/server/datatype/ReferenceValue.java
+++ b/src/main/java/caosdb/server/datatype/ReferenceValue.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -26,27 +28,36 @@ import caosdb.server.datatype.AbstractDatatype.Table;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.Message;
 import caosdb.server.utils.ServerMessages;
+import java.util.Objects;
 import org.jdom2.Element;
 
+/**
+ * A ReferenceValue represents the value of a reference property to another entity.
+ *
+ * <p>Differently from other properties, they may be versioned, i.e. they may reference to a
+ * specific version of an entity.
+ *
+ * <p>TODO: Ways to specify a reference value, what are the consequences of versioned references?
+ */
 public class ReferenceValue implements SingleValue {
   private EntityInterface entity = null;
   private String name = null;
   private Integer id = null;
+  private String version = null;
+  private boolean versioned = false;
 
   public static ReferenceValue parseReference(final Object reference) throws Message {
     if (reference == null) {
       return null;
     }
     if (reference instanceof EntityInterface) {
-      return new ReferenceValue((EntityInterface) reference);
+      return new ReferenceValue(
+          (EntityInterface) reference, ((EntityInterface) reference).hasVersion());
     } else if (reference instanceof ReferenceValue) {
       return (ReferenceValue) reference;
     } else if (reference instanceof GenericValue) {
-      try {
-        return new ReferenceValue(Integer.parseInt(((GenericValue) reference).toDatabaseString()));
-      } catch (final NumberFormatException e) {
-        return new ReferenceValue(((GenericValue) reference).toDatabaseString());
-      }
+      String str = ((GenericValue) reference).toDatabaseString();
+      return parseFromString(str);
     } else if (reference instanceof CollectionValue) {
       throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES;
     } else {
@@ -58,24 +69,81 @@ public class ReferenceValue implements SingleValue {
     }
   }
 
+  /**
+   * Split a reference string into an entity part and a version part, if there is a version part.
+   *
+   * <p>If parsing the entity ID part to an integer fails, a NumberFormatException may be thrown.
+   */
+  public static ReferenceValue parseIdVersion(String str) {
+    String[] split = str.split("@", 2);
+    if (split.length == 2) {
+      return new ReferenceValue(Integer.parseInt(split[0]), split[1]);
+    } else {
+      return new ReferenceValue(Integer.parseInt(str));
+    }
+  }
+
+  /**
+   * Create a ReferenceValue from a string.
+   *
+   * <p>If the string looks like a valid "entityID@version" string, the result will have the
+   * corresponding entity and version parts.
+   */
+  public static ReferenceValue parseFromString(String str) {
+    try {
+      return parseIdVersion(str);
+    } catch (final NumberFormatException e) {
+      return new ReferenceValue(str);
+    }
+  }
+
+  /**
+   * Produce a nice but short string:
+   *
+   * <p>Case 1 "versioned" (reference to an entity without specifying that entity's version):
+   * Produces a string like "1234" or "Experiment". Note that referencing via name is never
+   * versioned.
+   *
+   * <p>Case 2 "unversioned" (reference to an entity with a specified version): Produces a string
+   * like "1234@ab987f".
+   */
   @Override
   public String toString() {
-    if (this.entity != null) {
+    if (this.entity != null && versioned) { // Was specified as "versioned", with resolved entity
+      return this.entity.getIdVersion();
+    } else if (this.entity != null) { // resolved, but unversioned
       return this.entity.getId().toString();
-    } else if (this.id == null && this.name != null) {
+    } else if (this.id == null
+        && this.name != null) { // Only name is available, no id (and thus no resolved entity)
       return this.name;
     }
+    // Specification via id is the only remaining possibility
+    return getIdVersion(); // if version is null, returns ID only
+  }
+
+  public String getIdVersion() {
+    if (this.version != null) {
+      return new StringBuilder().append(this.id).append("@").append(this.version).toString();
+    }
     return this.id.toString();
   }
 
-  public ReferenceValue(final EntityInterface entity) {
+  public ReferenceValue(final EntityInterface entity, boolean versioned) {
+    this.versioned = versioned;
     this.entity = entity;
   }
 
   public ReferenceValue(final Integer id) {
+    this(id, null);
+  }
+
+  public ReferenceValue(final Integer id, final String version) {
     this.id = id;
+    this.version = version;
+    this.versioned = version != null;
   }
 
+  /** If the reference is given by name, versioning is not possible (at the moment). */
   public ReferenceValue(final String name) {
     this.name = name;
   }
@@ -84,7 +152,8 @@ public class ReferenceValue implements SingleValue {
     return this.entity;
   }
 
-  public final void setEntity(final EntityInterface entity) {
+  public final void setEntity(final EntityInterface entity, boolean versioned) {
+    this.versioned = versioned;
     this.entity = entity;
   }
 
@@ -102,6 +171,13 @@ public class ReferenceValue implements SingleValue {
     return this.id;
   }
 
+  public final String getVersion() {
+    if (this.entity != null && versioned && this.entity.hasVersion()) {
+      return this.entity.getVersion().getId();
+    }
+    return this.version;
+  }
+
   public final void setId(final Integer id) {
     this.id = id;
   }
@@ -126,7 +202,8 @@ public class ReferenceValue implements SingleValue {
     if (obj instanceof ReferenceValue) {
       final ReferenceValue that = (ReferenceValue) obj;
       if (that.getId() != null && getId() != null) {
-        return that.getId().equals(getId());
+        return that.getId().equals(getId())
+            && Objects.deepEquals(that.getVersion(), this.getVersion());
       } else if (that.getName() != null && getName() != null) {
         return that.getName().equals(getName());
       }
diff --git a/src/main/java/caosdb/server/entity/BaseEntity.java b/src/main/java/caosdb/server/entity/BaseEntity.java
deleted file mode 100644
index 62d4873b54c262eb75920f3f5c990e9988f07e50..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/entity/BaseEntity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.entity;
-
-class BaseEntity {
-
-  EntityID id = null;
-}
diff --git a/src/main/java/caosdb/server/entity/DeleteEntity.java b/src/main/java/caosdb/server/entity/DeleteEntity.java
index 29b6a3a075b60e811cfb0e66d066278a47d2edcb..b27ab3a7745a9b4909dac642a9fccbaf035f52f7 100644
--- a/src/main/java/caosdb/server/entity/DeleteEntity.java
+++ b/src/main/java/caosdb/server/entity/DeleteEntity.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -27,4 +29,9 @@ public class DeleteEntity extends Entity {
   public DeleteEntity(final int id) {
     super(id);
   }
+
+  public DeleteEntity(int id, String version) {
+    super(id);
+    setVersion(new Version(version));
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/Entity.java b/src/main/java/caosdb/server/entity/Entity.java
index 5c997d7ff8f9f98191ba768c82569a79ca056cf2..16f4f508e2d97a4c0f58c1775e95db9efbf4ffba 100644
--- a/src/main/java/caosdb/server/entity/Entity.java
+++ b/src/main/java/caosdb/server/entity/Entity.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,6 +24,7 @@
  */
 package caosdb.server.entity;
 
+import caosdb.datetime.UTCDateTime;
 import caosdb.server.CaosDBException;
 import caosdb.server.database.proto.SparseEntity;
 import caosdb.server.database.proto.VerySparseEntity;
@@ -521,6 +524,7 @@ public class Entity extends AbstractObservable implements EntityInterface {
     }
   }
 
+  // Strategy to convert this Entity to an XML element.
   private ToElementStrategy toElementStrategy = null;
 
   @Override
@@ -535,7 +539,12 @@ public class Entity extends AbstractObservable implements EntityInterface {
 
   @Override
   public final void addToElement(final Element element) {
-    getToElementStrategy().addToElement(this, element, new SetFieldStrategy(getSelections()));
+    addToElement(element, new SetFieldStrategy(getSelections()));
+  }
+
+  @Override
+  public void addToElement(Element element, SetFieldStrategy strategy) {
+    getToElementStrategy().addToElement(this, element, strategy);
   }
 
   /**
@@ -794,7 +803,11 @@ public class Entity extends AbstractObservable implements EntityInterface {
     final CollectionValue vals = new CollectionValue();
     int pidx = 0;
     for (final Element pe : element.getChildren()) {
-      if (pe.getName().equalsIgnoreCase("EmptyString")) {
+      if (pe.getName().equalsIgnoreCase("Version")) {
+        // IGNORE: Once it becomes allowed for clients to set a version id, parsing
+        // the Version element would be done here. Until this is the case, the
+        // Version tag is ignored.
+      } else if (pe.getName().equalsIgnoreCase("EmptyString")) {
         // special case: empty string which cannot be distinguished from null
         // values otherwise.
         setValue(new GenericValue(""));
@@ -1020,6 +1033,7 @@ public class Entity extends AbstractObservable implements EntityInterface {
   }
 
   private boolean datatypeOverride = false;
+  private Version version = new Version();
 
   @Override
   public EntityInterface setDatatypeOverride(final boolean b) {
@@ -1060,10 +1074,15 @@ public class Entity extends AbstractObservable implements EntityInterface {
   }
 
   @Override
-  public EntityInterface parseSparseEntity(final SparseEntity spe) {
+  public final EntityInterface parseSparseEntity(final SparseEntity spe) {
     setId(spe.id);
     this.setRole(spe.role);
     setEntityACL(spe.acl);
+    UTCDateTime versionDate = null;
+    if (spe.versionSeconds != null) {
+      versionDate = UTCDateTime.UTCSeconds(spe.versionSeconds, spe.versionNanos);
+    }
+    this.version = new Version(spe.version, versionDate);
 
     if (!isNameOverride()) {
       setName(spe.name);
@@ -1112,4 +1131,34 @@ public class Entity extends AbstractObservable implements EntityInterface {
   public boolean skipJob() {
     return false;
   }
+
+  @Override
+  public Version getVersion() {
+    return this.version;
+  }
+
+  @Override
+  public boolean hasVersion() {
+    return this.version.getId() != null;
+  }
+
+  @Override
+  public void setVersion(Version version) {
+    this.version = version;
+  }
+
+  /** Return "id@version" if there is versioning information, else only "id". */
+  @Override
+  public String getIdVersion() {
+    if (!this.hasId()) {
+      return null;
+    } else if (this.hasVersion()) {
+      return new StringBuilder()
+          .append(getId())
+          .append("@")
+          .append(getVersion().getId())
+          .toString();
+    }
+    return getId().toString();
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/EntityID.java b/src/main/java/caosdb/server/entity/EntityID.java
deleted file mode 100644
index fbfb83c8028f5f2e0d8376c6dc480c19046c0d5b..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/entity/EntityID.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.entity;
-
-public class EntityID {}
diff --git a/src/main/java/caosdb/server/entity/EntityInterface.java b/src/main/java/caosdb/server/entity/EntityInterface.java
index 21ed5cd85234bd633914302ce2982b2b6f4f9a66..4205111a58c30185d916c1d921acb7470d93f7a9 100644
--- a/src/main/java/caosdb/server/entity/EntityInterface.java
+++ b/src/main/java/caosdb/server/entity/EntityInterface.java
@@ -31,6 +31,7 @@ import caosdb.server.entity.container.PropertyContainer;
 import caosdb.server.entity.wrapper.Domain;
 import caosdb.server.entity.wrapper.Parent;
 import caosdb.server.entity.wrapper.Property;
+import caosdb.server.entity.xml.SetFieldStrategy;
 import caosdb.server.entity.xml.ToElementable;
 import caosdb.server.jobs.JobTarget;
 import caosdb.server.permissions.EntityACL;
@@ -40,6 +41,7 @@ import caosdb.unit.Unit;
 import java.util.List;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.subject.Subject;
+import org.jdom2.Element;
 
 public interface EntityInterface
     extends JobTarget, Observable, ToElementable, WriteEntity, TransactionEntity {
@@ -50,6 +52,8 @@ public interface EntityInterface
 
   public abstract Integer getId();
 
+  public abstract String getIdVersion();
+
   public abstract void setId(Integer id);
 
   public abstract boolean hasId();
@@ -182,4 +186,12 @@ public interface EntityInterface
   public abstract String getQueryTemplateDefinition();
 
   public abstract void setQueryTemplateDefinition(String query);
+
+  public abstract Version getVersion();
+
+  public abstract boolean hasVersion();
+
+  public abstract void setVersion(Version version);
+
+  public abstract void addToElement(Element element, SetFieldStrategy strategy);
 }
diff --git a/src/main/java/caosdb/server/entity/FileProperties.java b/src/main/java/caosdb/server/entity/FileProperties.java
index fbf8031f56566ea5a6df263d97611812bab113d0..c0fcaa468b8d2ff2ff353842d5c79c0fcf904f28 100644
--- a/src/main/java/caosdb/server/entity/FileProperties.java
+++ b/src/main/java/caosdb/server/entity/FileProperties.java
@@ -220,10 +220,10 @@ public class FileProperties {
     if (file.getAbsolutePath().startsWith(FileSystem.getBasepath())) {
       final Undoable d;
       final File parent = file.getParentFile();
-      if (file.getCanonicalPath().startsWith(FileSystem.getBasepath())) {
-        d = FileUtils.delete(file, file.isDirectory());
-      } else if (FileUtils.isSymlink(file)) {
+      if (FileUtils.isSymlink(file)) {
         d = FileUtils.unlink(file);
+      } else if (file.getCanonicalPath().startsWith(FileSystem.getBasepath())) {
+        d = FileUtils.delete(file, file.isDirectory());
       } else {
         throw new CaosDBException(
             "File is in Filesystem, but it is neither a normal file nor a symlink.");
diff --git a/src/main/java/caosdb/server/entity/NamedEntity.java b/src/main/java/caosdb/server/entity/NamedEntity.java
deleted file mode 100644
index 76ba79a317e146ca4b86793545f95d9e05e42480..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/entity/NamedEntity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.entity;
-
-class NamedEntity extends BaseEntity {
-
-  String name = null;
-}
diff --git a/src/main/java/caosdb/server/entity/RetrieveEntity.java b/src/main/java/caosdb/server/entity/RetrieveEntity.java
index 5b54c2bcd4ce401bc469728f774ad68198ba0986..7035232b5a66df77518c8c1b586b7bcb43b7a22b 100644
--- a/src/main/java/caosdb/server/entity/RetrieveEntity.java
+++ b/src/main/java/caosdb/server/entity/RetrieveEntity.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,9 +24,6 @@
  */
 package caosdb.server.entity;
 
-import caosdb.server.database.proto.SparseEntity;
-import caosdb.server.datatype.AbstractCollectionDatatype;
-
 public class RetrieveEntity extends Entity {
 
   public RetrieveEntity(final int id) {
@@ -35,38 +34,13 @@ public class RetrieveEntity extends Entity {
     super(name);
   }
 
-  public RetrieveEntity parseSparseEntity(final SparseEntity spe) {
-    setId(spe.id);
-    this.setRole(spe.role);
-    setEntityACL(spe.acl);
-
-    if (!isNameOverride()) {
-      setName(spe.name);
-    }
-    if (!isDescOverride()) {
-      setDescription(spe.description);
-    }
-    if (!isDatatypeOverride()) {
-      final String dt = spe.datatype;
-      final String col = spe.collection;
-
-      if (dt != null
-          && !dt.equalsIgnoreCase("null")
-          && (!hasDatatype() || !dt.equalsIgnoreCase(getDatatype().toString()))) {
-        if (col != null && !col.equalsIgnoreCase("null")) {
-          this.setDatatype(AbstractCollectionDatatype.collectionDatatypeFactory(col, dt));
-        } else {
-          this.setDatatype(dt);
-        }
-      }
-    }
-
-    if (spe.filePath != null) {
-      setFileProperties(new FileProperties(spe.fileHash, spe.filePath, spe.fileSize));
-    } else {
-      setFileProperties(null);
-    }
+  public RetrieveEntity(int id, String version) {
+    super(id);
+    this.setVersion(new Version(version));
+  }
 
-    return this;
+  public RetrieveEntity(String name, String version) {
+    super(name);
+    this.setVersion(new Version(version));
   }
 }
diff --git a/src/main/java/caosdb/server/entity/Version.java b/src/main/java/caosdb/server/entity/Version.java
new file mode 100644
index 0000000000000000000000000000000000000000..07c7387e8c419f962e1174504a28ceceda5c30e3
--- /dev/null
+++ b/src/main/java/caosdb/server/entity/Version.java
@@ -0,0 +1,84 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package caosdb.server.entity;
+
+import caosdb.datetime.UTCDateTime;
+import java.util.LinkedList;
+
+/**
+ * Plain old java object (POJO) for an entity's version.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public class Version {
+
+  private String id = null;
+  private LinkedList<Version> predecessors = null;
+  private LinkedList<Version> successors = null;
+  private UTCDateTime date = null;
+
+  public Version(String id, long seconds, int nanos) {
+    this(id, UTCDateTime.UTCSeconds(seconds, nanos));
+  }
+
+  public Version(String id, UTCDateTime date) {
+    this.id = id;
+    this.date = date;
+  }
+
+  public Version(String id) {
+    this(id, null);
+  }
+
+  public Version() {}
+
+  public UTCDateTime getDate() {
+    return date;
+  }
+
+  public void setDate(UTCDateTime date) {
+    this.date = date;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public LinkedList<Version> getSuccessors() {
+    return successors;
+  }
+
+  public void setSuccessors(LinkedList<Version> successors) {
+    this.successors = successors;
+  }
+
+  public LinkedList<Version> getPredecessors() {
+    return predecessors;
+  }
+
+  public void setPredecessors(LinkedList<Version> predecessors) {
+    this.predecessors = predecessors;
+  }
+}
diff --git a/src/main/java/caosdb/server/entity/container/Container.java b/src/main/java/caosdb/server/entity/container/Container.java
index 9ab0415d8c4ccc8767814eaa46a8cacfe74d594a..de20b6a3965fc4fead82612193d061e075d5217d 100644
--- a/src/main/java/caosdb/server/entity/container/Container.java
+++ b/src/main/java/caosdb/server/entity/container/Container.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -27,5 +29,17 @@ import java.util.ArrayList;
 
 public class Container<T extends EntityInterface> extends ArrayList<T> {
 
-  private static final long serialVersionUID = 3476714088253567549L;
+  private static final long serialVersionUID = 8519435849678175750L;
+
+  /**
+   * Return the entity with the matching id, if it can be found inside this Container, else null.
+   */
+  public T getEntityById(final Integer id) {
+    for (final T e : this) {
+      if (e.hasId() && e.getId().equals(id)) {
+        return e;
+      }
+    }
+    return null;
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/container/DeleteContainer.java b/src/main/java/caosdb/server/entity/container/DeleteContainer.java
index c219ee5e4399caeee857006f995748435d023963..c3d57bc656a7c9609dd8fb6ca4d031e0e82f63fa 100644
--- a/src/main/java/caosdb/server/entity/container/DeleteContainer.java
+++ b/src/main/java/caosdb/server/entity/container/DeleteContainer.java
@@ -42,4 +42,9 @@ public class DeleteContainer extends EntityByIdContainer {
   public void add(final int id) {
     add(new DeleteEntity(id));
   }
+
+  @Override
+  public void add(int id, String version) {
+    add(new DeleteEntity(id, version));
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java b/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java
index 1bfda2024c4ff11375ad619d64946e1036c714b0..29a0978b8acdc809b75c52de643ee131cd62b129 100644
--- a/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java
+++ b/src/main/java/caosdb/server/entity/container/EntityByIdContainer.java
@@ -37,4 +37,6 @@ public abstract class EntityByIdContainer extends TransactionContainer {
   }
 
   public abstract void add(int id);
+
+  public abstract void add(int id, String version);
 }
diff --git a/src/main/java/caosdb/server/entity/container/PropertyContainer.java b/src/main/java/caosdb/server/entity/container/PropertyContainer.java
index a80e2bfccea9f4b9c5cbe17fc32f9dd20b74c995..80cf816156c0d4d81bca1e5b0e0de19c38fcb032 100644
--- a/src/main/java/caosdb/server/entity/container/PropertyContainer.java
+++ b/src/main/java/caosdb/server/entity/container/PropertyContainer.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -50,6 +52,7 @@ public class PropertyContainer extends Container<Property> {
     this.s = new PropertyToElementStrategy();
   }
 
+  /** Sort the properties by their pidx (Property Index). */
   public void sort() {
     Collections.sort(
         this,
@@ -64,14 +67,32 @@ public class PropertyContainer extends Container<Property> {
         });
   }
 
-  public Element addToElement(final Element element, final SetFieldStrategy setFieldStrategy) {
+  /**
+   * Add a single property to the element using the given setFieldStrategy.
+   *
+   * @param property
+   * @param element
+   * @param setFieldStrategy
+   */
+  public void addToElement(
+      EntityInterface property, Element element, SetFieldStrategy setFieldStrategy) {
+    if (setFieldStrategy.isToBeSet(property.getName())) {
+      SetFieldStrategy strategy = setFieldStrategy.forProperty(property.getName());
+      this.s.addToElement(property, element, strategy);
+    }
+  }
+
+  /**
+   * Add all properties to the element using the given setFieldStrategy.
+   *
+   * @param element
+   * @param setFieldStrategy
+   */
+  public void addToElement(final Element element, final SetFieldStrategy setFieldStrategy) {
     sort();
     for (final EntityInterface property : this) {
-      if (setFieldStrategy.isToBeSet(property.getName())) {
-        this.s.addToElement(property, element, setFieldStrategy.forProperty(property.getName()));
-      }
+      addToElement(property, element, setFieldStrategy);
     }
-    return element;
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/entity/container/RetrieveContainer.java b/src/main/java/caosdb/server/entity/container/RetrieveContainer.java
index 9b4f7364d4ecdc63da1be8f1ead292545801a078..5b205bc27f38e3d1a04b2ed3c5a7e874af229489 100644
--- a/src/main/java/caosdb/server/entity/container/RetrieveContainer.java
+++ b/src/main/java/caosdb/server/entity/container/RetrieveContainer.java
@@ -46,4 +46,13 @@ public class RetrieveContainer extends EntityByIdContainer {
   public void add(final String name) {
     add(new RetrieveEntity(name));
   }
+
+  public void add(final String name, String version) {
+    add(new RetrieveEntity(name, version));
+  }
+
+  @Override
+  public void add(int id, String version) {
+    add(new RetrieveEntity(id, version));
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/container/TransactionContainer.java b/src/main/java/caosdb/server/entity/container/TransactionContainer.java
index f0ac55fae7b57d24456728508baa7768b83a5711..f3a8a0f395838e6c8f5b2a4d8adf6f5b8627d06c 100644
--- a/src/main/java/caosdb/server/entity/container/TransactionContainer.java
+++ b/src/main/java/caosdb/server/entity/container/TransactionContainer.java
@@ -145,15 +145,6 @@ public class TransactionContainer extends Container<EntityInterface>
     }
   }
 
-  public EntityInterface getEntityById(final Integer id) {
-    for (final EntityInterface e : this) {
-      if (e.hasId() && e.getId().equals(id)) {
-        return e;
-      }
-    }
-    return null;
-  }
-
   public Subject getOwner() {
     return this.owner;
   }
diff --git a/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java
index 4d419bbfc17aee5749db70976f1a510ae468032b..f8d563de1776d155b2f66b913de454c7e5b952fd 100644
--- a/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java
+++ b/src/main/java/caosdb/server/entity/wrapper/EntityWrapper.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -31,8 +33,10 @@ import caosdb.server.entity.FileProperties;
 import caosdb.server.entity.Message;
 import caosdb.server.entity.Role;
 import caosdb.server.entity.StatementStatus;
+import caosdb.server.entity.Version;
 import caosdb.server.entity.container.ParentContainer;
 import caosdb.server.entity.container.PropertyContainer;
+import caosdb.server.entity.xml.SetFieldStrategy;
 import caosdb.server.entity.xml.ToElementStrategy;
 import caosdb.server.entity.xml.ToElementable;
 import caosdb.server.permissions.EntityACL;
@@ -551,4 +555,29 @@ public class EntityWrapper implements EntityInterface {
   public boolean hasPermission(Subject subject, Permission permission) {
     return this.entity.hasPermission(subject, permission);
   }
+
+  @Override
+  public Version getVersion() {
+    return this.entity.getVersion();
+  }
+
+  @Override
+  public boolean hasVersion() {
+    return this.entity.hasVersion();
+  }
+
+  @Override
+  public void setVersion(Version version) {
+    this.entity.setVersion(version);
+  }
+
+  @Override
+  public String getIdVersion() {
+    return this.entity.getIdVersion();
+  }
+
+  @Override
+  public void addToElement(Element element, SetFieldStrategy strategy) {
+    this.entity.addToElement(element, strategy);
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/wrapper/Parent.java b/src/main/java/caosdb/server/entity/wrapper/Parent.java
index c36ffa9c2c1a16062add2eb912ca01132af9872c..f3686b5b802e004e55d572e58076e5e08bb5988d 100644
--- a/src/main/java/caosdb/server/entity/wrapper/Parent.java
+++ b/src/main/java/caosdb/server/entity/wrapper/Parent.java
@@ -57,4 +57,10 @@ public class Parent extends EntityWrapper {
   public Affiliation getAffiliation() {
     return this.affiliation;
   }
+
+  @Override
+  public boolean hasVersion() {
+    // parents are not versioned (yet).
+    return false;
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/wrapper/Property.java b/src/main/java/caosdb/server/entity/wrapper/Property.java
index f6bb840a75d9216ee3fd56f3d2bf95f701280492..1d1cd2a8ae11489479b9e9191365843182d136ca 100644
--- a/src/main/java/caosdb/server/entity/wrapper/Property.java
+++ b/src/main/java/caosdb/server/entity/wrapper/Property.java
@@ -134,4 +134,10 @@ public class Property extends EntityWrapper {
   public EntityInterface getDomainEntity() {
     return this.domain;
   }
+
+  @Override
+  public boolean hasVersion() {
+    // properties are not versioned (yet).
+    return false;
+  }
 }
diff --git a/src/main/java/caosdb/server/entity/xml/DomainToElementStrategy.java b/src/main/java/caosdb/server/entity/xml/DomainToElementStrategy.java
index 2559cfd12d7ca03344d03fa025ff2078d1bbcca0..099b51044353f3074ece5cef77a7588a658bab81 100644
--- a/src/main/java/caosdb/server/entity/xml/DomainToElementStrategy.java
+++ b/src/main/java/caosdb/server/entity/xml/DomainToElementStrategy.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -25,11 +27,22 @@ package caosdb.server.entity.xml;
 import caosdb.server.entity.EntityInterface;
 import org.jdom2.Element;
 
-public class DomainToElementStrategy implements ToElementStrategy {
+/**
+ * Generates a JDOM (XML) representation of an entity with role "Domain".
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public class DomainToElementStrategy extends EntityToElementStrategy {
+
+  public DomainToElementStrategy() {
+    super("Domain");
+  }
 
   @Override
   public Element toElement(final EntityInterface entity, final SetFieldStrategy setFieldStrategy) {
-    return EntityToElementStrategy.sparseEntityToElement("Domain", entity, setFieldStrategy);
+    Element element = new Element(tagName);
+    sparseEntityToElement(element, entity, setFieldStrategy);
+    return element;
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java
index 19bfe80c6b5a35b8cc214ffb6c260294633b8495..6cfe8fa9f3628cc0670ccb3e6f30a263a0bc5cf3 100644
--- a/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java
+++ b/src/main/java/caosdb/server/entity/xml/EntityToElementStrategy.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,27 +24,62 @@
  */
 package caosdb.server.entity.xml;
 
+import caosdb.server.datatype.ReferenceValue;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.Message;
 import caosdb.server.utils.EntityStatus;
 import caosdb.server.utils.TransactionLogMessage;
-import java.util.Comparator;
 import org.apache.shiro.SecurityUtils;
-import org.jdom2.Content;
-import org.jdom2.Content.CType;
+import org.jdom2.Attribute;
 import org.jdom2.Element;
 
+/**
+ * Base class for the generation of a JDOM (XML) representation for entities.
+ *
+ * <p>Record and RecordType entities use this class only. Properties, Parents, Files and other
+ * entities have specialized sub classes.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
 public class EntityToElementStrategy implements ToElementStrategy {
 
-  private final String tagName;
+  protected final String tagName;
 
   public EntityToElementStrategy(final String tagName) {
     this.tagName = tagName;
   }
 
-  public static Element sparseEntityToElement(
-      final String tagName, final EntityInterface entity, final SetFieldStrategy setFieldStrategy) {
-    final Element element = new Element(tagName);
+  /**
+   * Set the data type of this entity as a JDOM {@link Attribute} of the given element.
+   *
+   * <p>If the data type has a name, the name is used, otherwise the id is used.
+   *
+   * @param entity
+   * @param element
+   */
+  public void setDatatype(EntityInterface entity, Element element) {
+    if (entity.getDatatype().getName() != null) {
+      element.setAttribute("datatype", entity.getDatatype().getName());
+    } else {
+      element.setAttribute("datatype", entity.getDatatype().getId().toString());
+    }
+  }
+
+  /**
+   * Set all properties of the entity that are considered to be part of the sparse entity, e.g.
+   * name, description, etc, as {@link Attribute} of the given element.
+   *
+   * <p>The setFieldStrategy decides which attributes are set if present and which are omitted in
+   * any case.
+   *
+   * @param element
+   * @param entity
+   * @param setFieldStrategy
+   */
+  public void sparseEntityToElement(
+      final Element element,
+      final EntityInterface entity,
+      final SetFieldStrategy setFieldStrategy) {
 
     if (entity.getEntityACL() != null) {
       element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject()));
@@ -50,6 +87,10 @@ public class EntityToElementStrategy implements ToElementStrategy {
     if (setFieldStrategy.isToBeSet("id") && entity.hasId()) {
       element.setAttribute("id", Integer.toString(entity.getId()));
     }
+    if (setFieldStrategy.isToBeSet("version") && entity.hasVersion()) {
+      Element v = new VersionXMLSerializer().toElement(entity.getVersion());
+      element.addContent(v);
+    }
     if (setFieldStrategy.isToBeSet("cuid") && entity.hasCuid()) {
       element.setAttribute("cuid", entity.getCuid());
     }
@@ -60,11 +101,7 @@ public class EntityToElementStrategy implements ToElementStrategy {
       element.setAttribute("description", entity.getDescription());
     }
     if (setFieldStrategy.isToBeSet("datatype") && entity.hasDatatype()) {
-      if (entity.getDatatype().getName() != null) {
-        element.setAttribute("datatype", entity.getDatatype().getName());
-      } else {
-        element.setAttribute("datatype", entity.getDatatype().getId().toString());
-      }
+      setDatatype(entity, element);
     }
     if (setFieldStrategy.isToBeSet("message") && entity.hasMessages()) {
       for (final ToElementable m : entity.getMessages()) {
@@ -76,55 +113,78 @@ public class EntityToElementStrategy implements ToElementStrategy {
       q.setText(entity.getQueryTemplateDefinition());
       element.addContent(q);
     }
+  }
 
-    return element;
+  /**
+   * Set the value of the entity.
+   *
+   * <p>The setFieldStrategy decides if the value is to be set at all.
+   *
+   * <p>If the value is a reference, the setFieldStrategy decides whether the referenced entity is
+   * added as a deep Element tree (as a whole, so to speak) or just the ID of the referenced entity.
+   *
+   * @param entity
+   * @param element
+   * @param setFieldStrategy
+   */
+  public void setValue(EntityInterface entity, Element element, SetFieldStrategy setFieldStrategy) {
+    if (entity.hasValue()) {
+      try {
+        entity.parseValue();
+      } catch (final Message | NullPointerException e) {
+        // Ignore. Parsing the value failed. But that does not concern us here, because this is the
+        // case when a write transaction failed. The error for that has already been handled by the
+        // CheckValueParsable job.
+      }
+
+      if (entity.getValue() instanceof ReferenceValue
+          && setFieldStrategy.isToBeSet("_referenced")) {
+        // Append the complete entity. This needs to be done when we are
+        // processing SELECT Queries.
+        EntityInterface ref = ((ReferenceValue) entity.getValue()).getEntity();
+        if (ref != null) {
+          if (entity.hasDatatype()) {
+            setDatatype(entity, element);
+          }
+          ref.addToElement(element, setFieldStrategy);
+          // the referenced entity has been appended. Return here to suppress
+          // adding the reference id as well.
+          return;
+        }
+      }
+
+      if (setFieldStrategy.isToBeSet("value")) {
+        if (entity.hasDatatype()) {
+          setDatatype(entity, element);
+        }
+        entity.getValue().addToElement(element);
+      }
+    }
   }
 
   @Override
   public Element toElement(final EntityInterface entity, final SetFieldStrategy setFieldStrategy) {
-    final Element element = sparseEntityToElement(this.tagName, entity, setFieldStrategy);
+    final Element element = new Element(tagName);
+
+    // always have the values at the beginning of the children
+    setValue(entity, element, setFieldStrategy);
 
-    if (setFieldStrategy.isToBeSet("importance") && entity.hasStatementStatus()) {
+    sparseEntityToElement(element, entity, setFieldStrategy);
+
+    if (entity.hasStatementStatus() && setFieldStrategy.isToBeSet("importance")) {
       element.setAttribute("importance", entity.getStatementStatus().toString());
     }
-    if (setFieldStrategy.isToBeSet("parent") && entity.hasParents()) {
+    if (entity.hasParents() && setFieldStrategy.isToBeSet("parent")) {
       entity.getParents().addToElement(element);
     }
     if (entity.hasProperties()) {
       entity.getProperties().addToElement(element, setFieldStrategy);
     }
-    if (setFieldStrategy.isToBeSet("history") && entity.hasTransactionLogMessages()) {
+    if (entity.hasTransactionLogMessages() && setFieldStrategy.isToBeSet("history")) {
       for (final TransactionLogMessage t : entity.getTransactionLogMessages()) {
         t.xmlAppendTo(element);
       }
     }
-    if (setFieldStrategy.isToBeSet("value") && entity.hasValue()) {
-      if (entity.hasDatatype()) {
-        try {
-
-          entity.getDatatype().parseValue(entity.getValue()).addToElement(element);
-        } catch (final Message e) {
-          //
-        }
-      } else {
-        entity.getValue().addToElement(element);
-      }
-      // put value at first position
-      element.sortContent(
-          new Comparator<Content>() {
-
-            @Override
-            public int compare(final Content o1, final Content o2) {
-              if (o1.getCType() == CType.CDATA || o1.getCType() == CType.Text) {
-                return -1;
-              }
-              if (o2.getCType() == CType.CDATA || o2.getCType() == CType.Text) {
-                return 1;
-              }
-              return 0;
-            }
-          });
-    }
     return element;
   }
 
diff --git a/src/main/java/caosdb/server/entity/xml/ParentToElementStrategy.java b/src/main/java/caosdb/server/entity/xml/ParentToElementStrategy.java
index 0d79951f5554b4c2c346224f3b6e91c1c056a96e..2498e00bfda219eb5e724a7c38bc73cc4ad3468e 100644
--- a/src/main/java/caosdb/server/entity/xml/ParentToElementStrategy.java
+++ b/src/main/java/caosdb/server/entity/xml/ParentToElementStrategy.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -27,17 +29,26 @@ import caosdb.server.entity.wrapper.Parent;
 import caosdb.server.utils.EntityStatus;
 import org.jdom2.Element;
 
-public class ParentToElementStrategy implements ToElementStrategy {
+/**
+ * Generates a JDOM (XML) representation of an entity's parent.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+public class ParentToElementStrategy extends EntityToElementStrategy {
+
+  public ParentToElementStrategy() {
+    super("Parent");
+  }
 
   @Override
   public Element toElement(final EntityInterface entity, final SetFieldStrategy setFieldStrategy) {
-    final Element e =
-        EntityToElementStrategy.sparseEntityToElement("Parent", entity, setFieldStrategy);
+    final Element element = new Element(this.tagName);
+    sparseEntityToElement(element, entity, setFieldStrategy);
     final Parent parent = (Parent) entity;
     if (parent.getAffiliation() != null) {
-      e.setAttribute("affiliation", parent.getAffiliation().toString());
+      element.setAttribute("affiliation", parent.getAffiliation().toString());
     }
-    return e;
+    return element;
   }
 
   @Override
diff --git a/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java b/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java
index 7299659900014c4800b3fe0b8864cd7d15409380..7a4a29a16cc545ca5316c7b086a8b1ec46cfcfc3 100644
--- a/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java
+++ b/src/main/java/caosdb/server/entity/xml/SetFieldStrategy.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,20 +25,34 @@
 package caosdb.server.entity.xml;
 
 import caosdb.server.entity.EntityInterface;
+import caosdb.server.query.Query;
 import caosdb.server.query.Query.Selection;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 
+/**
+ * A class which decides whether the properties, parents, name, etc. of an entity are to be included
+ * into the serialization or not.
+ *
+ * <p>The decision is based on a list of {@link Query.Selection} or smart defaults.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
 public class SetFieldStrategy {
 
   private final List<Selection> selections = new LinkedList<Selection>();
   private HashMap<String, Boolean> cache = null;
+
+  /**
+   * The default is: Any field should be included into the serialization, unless it is a referenced
+   * entity.
+   */
   private static final SetFieldStrategy defaultSelections =
       new SetFieldStrategy(null) {
         @Override
         public boolean isToBeSet(final String field) {
-          return true;
+          return field == null || !field.equalsIgnoreCase("_referenced");
         }
       };
 
@@ -68,8 +84,9 @@ public class SetFieldStrategy {
     return forProperty(property.getName());
   }
 
+  /** Return the strategy for a property. */
   public SetFieldStrategy forProperty(final String name) {
-    // if property is to be omitted.
+    // if property is to be omitted: always-false-strategy
     if (!isToBeSet(name)) {
       return new SetFieldStrategy() {
         @Override
@@ -85,6 +102,23 @@ public class SetFieldStrategy {
         subselections.add(s.getSubselection());
       }
     }
+    if (subselections.isEmpty() && this.isToBeSet("_referenced")) {
+
+      /**
+       * If the super selection decided that the referenced entity is to be included into the
+       * serialization, while it doesn't specify the subselects, every field is to be included.
+       *
+       * <p>This is the case when the selections are deeply nested but only the very last segments
+       * are actually used, e.g ["a.b.c.d1", "a.b.c.d2"].
+       */
+      return new SetFieldStrategy() {
+        // Return true for everything except version fields.
+        @Override
+        public boolean isToBeSet(String field) {
+          return field == null || !field.equalsIgnoreCase("version");
+        }
+      };
+    }
     return new SetFieldStrategy(subselections);
   }
 
@@ -94,13 +128,23 @@ public class SetFieldStrategy {
       return defaultSelections.isToBeSet(field);
     }
 
+    // There must be a least one selection defined, a null field won't match anything.
+    if (field == null) {
+      return false;
+    }
+
     if (this.cache == null) {
       this.cache = new HashMap<String, Boolean>();
-      // fill cache
+      // always include the id and the name
       this.cache.put("id", true);
+      this.cache.put("name", true);
+
+      // ... and the referenced entity.
+      this.cache.put("_referenced", true);
       for (final Selection selection : this.selections) {
-        if (!selection.getSelector().equals("id")) {
-          this.cache.put("name", true);
+        if (selection.getSelector().equals("value")) {
+          // if the value is present, the data type is needed as well
+          this.cache.put("datatype", true);
         }
         this.cache.put(selection.getSelector().toLowerCase(), true);
       }
diff --git a/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java b/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..badd1a95f2a2338bde43a1de23774097ab2c45ed
--- /dev/null
+++ b/src/main/java/caosdb/server/entity/xml/VersionXMLSerializer.java
@@ -0,0 +1,57 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package caosdb.server.entity.xml;
+
+import caosdb.server.entity.Version;
+import java.util.TimeZone;
+import org.jdom2.Element;
+
+/**
+ * Creates a JDOM Element for a Version instance.
+ *
+ * @author Timm Fitschen <t.fitschen@indiscale.com>
+ */
+class VersionXMLSerializer {
+  public Element toElement(Version version) {
+    Element result = new Element("Version");
+    result.setAttribute("id", version.getId());
+    if (version.getDate() != null) {
+      result.setAttribute("date", version.getDate().toDateTimeString(TimeZone.getDefault()));
+    }
+    if (version.getPredecessors() != null) {
+      for (Version p : version.getPredecessors()) {
+        Element predecessor = new Element("Predecessor");
+        predecessor.setAttribute("id", p.getId());
+        predecessor.setAttribute("date", p.getDate().toDateTimeString(TimeZone.getDefault()));
+        result.addContent(predecessor);
+      }
+    }
+    if (version.getSuccessors() != null) {
+      for (Version s : version.getSuccessors()) {
+        Element successor = new Element("Successor");
+        successor.setAttribute("id", s.getId());
+        successor.setAttribute("date", s.getDate().toDateTimeString(TimeZone.getDefault()));
+        result.addContent(successor);
+      }
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/caosdb/server/jobs/Job.java b/src/main/java/caosdb/server/jobs/Job.java
index 110809e77ae0d40076642d953c15b0d9566eddd6..ff214b977dfd870a9aa56b3e8efa4f12967b2083 100644
--- a/src/main/java/caosdb/server/jobs/Job.java
+++ b/src/main/java/caosdb/server/jobs/Job.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -171,11 +173,39 @@ public abstract class Job extends AbstractObservable implements Observer {
 
   protected final EntityInterface retrieveValidSparseEntityByName(final String name)
       throws Message {
-    return retrieveValidSparseEntityById(retrieveValidIDByName(name));
+    return retrieveValidSparseEntityById(retrieveValidIDByName(name), null);
   }
 
-  protected final EntityInterface retrieveValidSparseEntityById(final Integer id) throws Message {
-    final EntityInterface ret = execute(new RetrieveSparseEntity(id)).getEntity();
+  protected final EntityInterface retrieveValidSparseEntityById(
+      final Integer id, final String version) throws Message {
+
+    String resulting_version = version;
+    if (version == null || version.equals("HEAD")) {
+      // the targeted entity version is the entity after the transaction or the
+      // entity without a specific version. Thus we have to fetch the entity
+      // from the container if possible.
+      EntityInterface ret = getEntityById(id);
+      if (ret != null) {
+        return ret;
+      }
+    } else if (version.startsWith("HEAD~")) {
+      EntityInterface entById = getEntityById(id);
+      if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) {
+        // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be
+        // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD
+        // *after* the successful transaction, so that it is consistent with subsequent retrieves.
+        int offset = Integer.parseInt(version.substring(5)) - 1;
+        if (offset == 0) {
+          // special case HEAD~1
+          resulting_version = "HEAD";
+        } else {
+          resulting_version = new StringBuilder().append("HEAD~").append(offset).toString();
+        }
+      }
+    }
+
+    final EntityInterface ret =
+        execute(new RetrieveSparseEntity(id, resulting_version)).getEntity();
     if (ret.getEntityStatus() == EntityStatus.NONEXISTENT) {
       throw ServerMessages.ENTITY_DOES_NOT_EXIST;
     }
diff --git a/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java
index 69d36f42fa2f457b05dcc394659b5c088976bff4..faf481dba5ac16836b4495d6112fc25c887a6f1f 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckDatatypePresent.java
@@ -138,7 +138,8 @@ public final class CheckDatatypePresent extends EntityJob {
       }
     } else {
 
-      final EntityInterface validDatatypeEntity = retrieveValidSparseEntityById(datatype.getId());
+      final EntityInterface validDatatypeEntity =
+          retrieveValidSparseEntityById(datatype.getId(), null);
       assertAllowedToUse(validDatatypeEntity);
       datatype.setEntity(validDatatypeEntity);
     }
@@ -151,7 +152,7 @@ public final class CheckDatatypePresent extends EntityJob {
   private void checkIfOverride() throws Message {
     if (getEntity().hasId() && getEntity().getId() > 0) {
       // get data type from database
-      final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId());
+      final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null);
 
       if (foreign.hasDatatype() && !foreign.getDatatype().equals(getEntity().getDatatype())) {
         // is override!
@@ -179,7 +180,7 @@ public final class CheckDatatypePresent extends EntityJob {
     // the data type of the corresponding abstract property.
     if (getEntity().hasId() && getEntity().getId() > 0) {
       // get from data base
-      final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId());
+      final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null);
       inheritDatatypeFromForeignEntity(foreign);
     } else if (getEntity().hasId() && getEntity().getId() < 0) {
       // get from container
@@ -218,7 +219,7 @@ public final class CheckDatatypePresent extends EntityJob {
     for (final EntityInterface parent : getEntity().getParents()) {
       EntityInterface parentEntity = null;
       if (parent.getId() > 0) {
-        parentEntity = retrieveValidSparseEntityById(parent.getId());
+        parentEntity = retrieveValidSparseEntityById(parent.getId(), null);
       } else {
         parentEntity = getEntityById(parent.getId());
         runJobFromSchedule(parentEntity, CheckDatatypePresent.class);
diff --git a/src/main/java/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/caosdb/server/jobs/core/CheckParValid.java
index 005053a4a7d59c6c9f1a05171085701c606970b5..560558dce6e494fa4857fefadfa7d22ef79ee5aa 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckParValid.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckParValid.java
@@ -65,7 +65,7 @@ public class CheckParValid extends EntityJob {
             if (parent.getId() >= 0) {
               // id >= 0 (parent is yet in the database)
               // retrieve parent by id
-              final EntityInterface foreign = retrieveValidSparseEntityById(parent.getId());
+              final EntityInterface foreign = retrieveValidSparseEntityById(parent.getId(), null);
               // check permissions for this
               // parentforeign.acceptObserver(o)
               assertAllowedToUse(foreign);
diff --git a/src/main/java/caosdb/server/jobs/core/CheckPropValid.java b/src/main/java/caosdb/server/jobs/core/CheckPropValid.java
index 6a7cbb51556dd0b9c634da4c8a56d20204fc9f50..5e199e848984a4d042c3a755d83a8d57ba91987f 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckPropValid.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckPropValid.java
@@ -55,7 +55,7 @@ public class CheckPropValid extends EntityJob {
             if (property.getId() >= 0) {
 
               final EntityInterface abstractProperty =
-                  retrieveValidSparseEntityById(property.getId());
+                  retrieveValidSparseEntityById(property.getId(), null);
 
               assertAllowedToUse(abstractProperty);
 
diff --git a/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java b/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
index fa2b21ccaea7ba3c8bd2b733f8420c213e4f03f7..04a1ac8c5bf6aa42bf2dcec803e6bc1b68a3a7f0 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckRefidIsaParRefid.java
@@ -94,7 +94,8 @@ public class CheckRefidIsaParRefid extends EntityJob {
                   && getEntityByName(rv.getName()).getRole() == Role.File) {
               } else if (rv.getId() != null
                   && rv.getId() > 0
-                  && retrieveValidSparseEntityById(rv.getId()).getRole() == Role.File) {
+                  && retrieveValidSparseEntityById(rv.getId(), rv.getVersion()).getRole()
+                      == Role.File) {
               } else if (rv.getName() != null
                   && retrieveValidSparseEntityByName(rv.getName()).getRole() == Role.File) {
               } else {
diff --git a/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java b/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java
index 14d98962f48c300ae77fe0811922bf3b1d7eb4ae..36591070cd64558114bb56e22860b95f42fc6463 100644
--- a/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java
+++ b/src/main/java/caosdb/server/jobs/core/CheckRefidValid.java
@@ -3,7 +3,9 @@
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -85,9 +87,12 @@ public class CheckRefidValid extends EntityJob {
   private void checkRefValue(final ReferenceValue ref) throws Message {
     if (ref.getId() != null) {
       if (ref.getId() >= 0) {
-        final EntityInterface referencedValidEntity = retrieveValidSparseEntityById(ref.getId());
+        final EntityInterface referencedValidEntity =
+            retrieveValidSparseEntityById(ref.getId(), ref.getVersion());
         assertAllowedToUse(referencedValidEntity);
-        ref.setEntity(referencedValidEntity);
+
+        // link the entity as versioned entity iff the reference specified a version
+        ref.setEntity(referencedValidEntity, ref.getVersion() != null);
 
       } else {
 
@@ -100,7 +105,9 @@ public class CheckRefidValid extends EntityJob {
           final EntityInterface referencedEntity = getEntityById(ref.getId());
           if (referencedEntity != null) {
             assertAllowedToUse(referencedEntity);
-            ref.setEntity(referencedEntity);
+
+            // link the entity as versioned entity iff the reference specified a version
+            ref.setEntity(referencedEntity, ref.getVersion() != null);
           } else {
             throw ServerMessages.REFERENCED_ENTITY_DOES_NOT_EXIST;
           }
@@ -117,7 +124,9 @@ public class CheckRefidValid extends EntityJob {
 
         if (referencedEntity != null) {
           assertAllowedToUse(referencedEntity);
-          ref.setEntity(referencedEntity);
+
+          // link the entity as versioned entity iff the reference specified a version
+          ref.setEntity(referencedEntity, ref.getVersion() != null);
           if (checkRefEntity(ref)) {
             ref.getEntity().acceptObserver(this);
           }
@@ -125,7 +134,9 @@ public class CheckRefidValid extends EntityJob {
           final EntityInterface referencedValidEntity =
               retrieveValidSparseEntityByName(ref.getName());
           assertAllowedToUse(referencedValidEntity);
-          ref.setEntity(referencedValidEntity);
+
+          // link the entity as versioned entity iff the reference specified a version
+          ref.setEntity(referencedValidEntity, ref.getVersion() != null);
         }
       }
     }
diff --git a/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java b/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java
index a104c124293cf07ecc3b871c4bc6b31470ec2a15..a09f66533cb3bc3c7cc01b2d6931bc120f87f7a7 100644
--- a/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java
+++ b/src/main/java/caosdb/server/jobs/core/RemoveDuplicates.java
@@ -30,17 +30,20 @@ public class RemoveDuplicates extends ContainerJob {
 
   @Override
   protected void run() {
-    final HashSet<EntityInterface> rm = new HashSet<EntityInterface>();
+    // collect duplicates
+    final HashSet<EntityInterface> duplicates = new HashSet<EntityInterface>();
     for (final EntityInterface e : getContainer()) {
-      if (e.hasId() && !rm.contains(e)) {
+      if (e.hasId() && !duplicates.contains(e)) {
         for (final EntityInterface e2 : getContainer()) {
-          if (e2 != e && e.getId().equals(e2.getId())) {
-            rm.add(e2);
+          if (e2 != e && e.getIdVersion().equals(e2.getIdVersion())) {
+            // this is a duplicate of another entity in this container
+            duplicates.add(e2);
           }
         }
       }
     }
-    for (final EntityInterface e : rm) {
+    // remove duplicates.
+    for (final EntityInterface e : duplicates) {
       getContainer().remove(e);
     }
   }
diff --git a/src/main/java/caosdb/server/permissions/EntityACL.java b/src/main/java/caosdb/server/permissions/EntityACL.java
index 34ef34351826facfa7d342d6c8b06e8d8c27cf74..e0bfcf9243eb436f9af0d6a3bacfabb79984538c 100644
--- a/src/main/java/caosdb/server/permissions/EntityACL.java
+++ b/src/main/java/caosdb/server/permissions/EntityACL.java
@@ -92,7 +92,11 @@ public class EntityACL {
   }
 
   public static final EntityACL getOwnerACLFor(final Subject subject) {
-    if (subject.getPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) {
+    // TODO handle the case where a valid non-guest user (e.g. me@PAM,
+    // you@CaosDB) is (just temporarily) authenticated via a
+    // OneTimeAuthenticationToken
+    if (AuthenticationUtils.isAnonymous(subject)
+        || AuthenticationUtils.isFromOneTimeTokenRealm(subject)) {
       return new EntityACLFactory().create();
     }
     return getOwnerACLFor((Principal) subject.getPrincipal());
diff --git a/src/main/java/caosdb/server/permissions/Role.java b/src/main/java/caosdb/server/permissions/Role.java
index 70e1a61f754b4beeffe8f8fe203b42842d49cb6d..87b5d2474c4d2d3f13fd2787aaf5b11a1245a39c 100644
--- a/src/main/java/caosdb/server/permissions/Role.java
+++ b/src/main/java/caosdb/server/permissions/Role.java
@@ -22,7 +22,6 @@
  */
 package caosdb.server.permissions;
 
-import caosdb.server.accessControl.UserSources;
 import java.util.HashMap;
 import org.jdom2.Attribute;
 import org.jdom2.Element;
@@ -31,7 +30,7 @@ public class Role implements ResponsibleAgent {
 
   public static final Role OWNER_ROLE = new Role("?OWNER?");
   public static final Role OTHER_ROLE = new Role("?OTHER?");
-  public static final Role ANONYMOUS_ROLE = new Role(UserSources.ANONYMOUS_ROLE);
+  public static final Role ANONYMOUS_ROLE = new Role("anonymous");
 
   private final String role;
 
diff --git a/src/main/java/caosdb/server/query/CQLParser.g4 b/src/main/java/caosdb/server/query/CQLParser.g4
index af981d6a2d027892c144070323aa924a5d7674a5..8b5e5a94aaea5d62ba60068a1ad652f7b51f1bd4 100644
--- a/src/main/java/caosdb/server/query/CQLParser.g4
+++ b/src/main/java/caosdb/server/query/CQLParser.g4
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -188,11 +190,11 @@ username returns [Query.Pattern ep] locals [int type]
 
 transaction_time returns [String tqp]
 :
-	(
-		(ON | IN) 
-		(datetime {$tqp = $datetime.text;} 
-		| entity {$tqp = $entity.ep.toString();})
-	) | TODAY {$tqp = TransactionFilter.TODAY;} 
+    (
+        (ON | IN)
+        (value {$tqp = $value.text;}
+        | entity {$tqp = $entity.ep.toString();})
+    ) | TODAY {$tqp = TransactionFilter.TODAY;}
 ;
 
 /*
@@ -244,8 +246,8 @@ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a]
 			)
 			| IS_NULL {$o = "0";}
 			| IS_NOT_NULL {$o = "!0";}
-			| IN datetime {$o = "("; $v=$datetime.text;}
-			| NEGATION IN datetime {$o = "!("; $v=$datetime.text;}
+			| IN value {$o = "("; $v=$value.str;}
+			| NEGATION IN value {$o = "!("; $v=$value.str;}
 		)?
 	) 
 	| 
@@ -443,7 +445,7 @@ minmax returns [String agg]
 value returns [String str]
 : 
 	number {$str = $text;}
-	| datetime {$str = $text;}
+	| datetime {$str = $datetime.text;}
 	| atom {$str = $atom.ep.toString();}
 ;
 
diff --git a/src/main/java/caosdb/server/query/POV.java b/src/main/java/caosdb/server/query/POV.java
index a50be918da30d6cb69b457ef4b7801ea3019d58d..924c1293bfea78ef15f1b6ab10e5d9d91a2a98cc 100644
--- a/src/main/java/caosdb/server/query/POV.java
+++ b/src/main/java/caosdb/server/query/POV.java
@@ -167,6 +167,9 @@ public class POV implements EntityFilterInterface {
       } catch (final ClassCastException e) {
         this.vDatetime = null;
       } catch (final IllegalArgumentException e) {
+        if (this.operator.contains("(")) {
+          throw new Query.ParsingException("the value is expected to be a date time");
+        }
         this.vDatetime = null;
       }
     } else {
diff --git a/src/main/java/caosdb/server/query/Query.java b/src/main/java/caosdb/server/query/Query.java
index 1f85826967cf5020cdbdb6ecbbfe4e9452b9d929..a2d6e48767f5835bd9bf40ce65717bf9e8363f98 100644
--- a/src/main/java/caosdb/server/query/Query.java
+++ b/src/main/java/caosdb/server/query/Query.java
@@ -66,18 +66,20 @@ import org.jdom2.Element;
 
 public class Query implements QueryInterface, ToElementable, TransactionInterface {
 
+  /** Class which represents the selection of (sub)properties. */
   public static class Selection {
     private final String selector;
     private Selection subselection = null;
 
     public Selection setSubSelection(final Selection sub) {
       if (this.subselection != null) {
-        throw new UnsupportedOperationException("subselection is immutble!");
+        throw new UnsupportedOperationException("SubSelection is immutable!");
       }
       this.subselection = sub;
       return this;
     }
 
+    /** No parsing, just sets the selector string. */
     public Selection(final String selector) {
       this.selector = selector.trim();
     }
@@ -299,7 +301,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
 
         // ... check for RETRIEVE:ENTITY permission...
         final EntityInterface e =
-            execute(new RetrieveSparseEntity(q.getKey()), query.getAccess()).getEntity();
+            execute(new RetrieveSparseEntity(q.getKey(), null), query.getAccess()).getEntity();
         final EntityACL entityACL = e.getEntityACL();
         if (!entityACL.isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
           // ... and ignore if not.
@@ -553,7 +555,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
       while (rs.next()) {
         final long t1 = System.currentTimeMillis();
         final Integer id = rs.getInt("id");
-        if (!execute(new RetrieveSparseEntity(id), query.getAccess())
+        if (!execute(new RetrieveSparseEntity(id, null), query.getAccess())
             .getEntity()
             .getEntityACL()
             .isPermitted(query.getUser(), EntityPermission.RETRIEVE_ENTITY)) {
@@ -586,7 +588,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac
     while (iterator.hasNext()) {
       final long t1 = System.currentTimeMillis();
       final Integer id = iterator.next();
-      if (!execute(new RetrieveSparseEntity(id), getAccess())
+      if (!execute(new RetrieveSparseEntity(id, null), getAccess())
           .getEntity()
           .getEntityACL()
           .isPermitted(getUser(), EntityPermission.RETRIEVE_ENTITY)) {
diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
index ea4e65f0a60d72cb5da6cb03b2cec44848dbc3c6..97488a6ba2a7984d66bb94332b4d7df0f9478cfb 100644
--- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
+++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java
@@ -4,7 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- * Copyright (C) 2019 IndiScale GmbH
+ * Copyright (C) 2019,2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,13 +24,11 @@
  */
 package caosdb.server.resource;
 
-import static caosdb.server.utils.Utils.isNonNullInteger;
 import static java.net.URLDecoder.decode;
 
 import caosdb.server.CaosDBException;
 import caosdb.server.accessControl.AuthenticationUtils;
 import caosdb.server.accessControl.Principal;
-import caosdb.server.accessControl.UserSources;
 import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import caosdb.server.entity.Message;
 import caosdb.server.utils.ServerMessages;
@@ -39,7 +38,6 @@ import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.sql.SQLException;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -78,9 +76,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
   private static final XMLParser xmlparser = new XMLParser();
   protected String sRID = null; // Server side request ID
   private String cRID = null; // Client side request ID
-  private String[] requestedItems = null;
-  private ArrayList<Integer> requestedIDs = new ArrayList<Integer>();
-  private ArrayList<String> requestedNames = new ArrayList<String>();
+  private String[] requestedItems = {};
   private WebinterfaceUtils utils;
 
   /** Return the {@link WebinterfaceUtils} instance for this resource. */
@@ -142,21 +138,6 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
       }
 
       this.requestedItems = specifier.split("&");
-      for (final String requestedItem : this.requestedItems) {
-        if (isNonNullInteger(requestedItem)) {
-          final int id = Integer.parseInt(requestedItem);
-          if (id > 0) {
-            getRequestedIDs().add(id);
-          }
-        } else if (requestedItem.equalsIgnoreCase("all")) {
-          getRequestedNames().clear();
-          getRequestedIDs().clear();
-          getRequestedNames().add("all");
-          break;
-        } else {
-          getRequestedNames().add(requestedItem);
-        }
-      }
     }
 
     // flags
@@ -210,13 +191,11 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
 
     if (user != null && user.isAuthenticated()) {
       Element userInfo = new Element("UserInfo");
-      if (!user.getPrincipal().equals(AuthenticationUtils.ANONYMOUS_USER.getPrincipal())) {
-        // TODO: deprecated
-        addNameAndRealm(retRoot, user);
+      // TODO: deprecated, needs refactoring in the webui first
+      addNameAndRealm(retRoot, user);
 
-        // this is the new, correct way
-        addNameAndRealm(userInfo, user);
-      }
+      // this is the new, correct way
+      addNameAndRealm(userInfo, user);
 
       addRoles(userInfo, user);
       retRoot.addContent(userInfo);
@@ -231,7 +210,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
    * @param user
    */
   private void addRoles(Element userInfo, Subject user) {
-    Collection<String> roles = UserSources.resolve(user.getPrincipals());
+    Collection<String> roles = AuthenticationUtils.getRoles(user);
     if (roles == null) return;
     Element rs = new Element("Roles");
     for (String role : roles) {
@@ -248,8 +227,10 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
    * @param userInfo
    */
   private void addNameAndRealm(Element userInfo, Subject user) {
-    userInfo.setAttribute("username", ((Principal) user.getPrincipal()).getUsername());
-    userInfo.setAttribute("realm", ((Principal) user.getPrincipal()).getRealm());
+    if (!AuthenticationUtils.isAnonymous((Principal) user.getPrincipal())) {
+      userInfo.setAttribute("username", ((Principal) user.getPrincipal()).getUsername());
+      userInfo.setAttribute("realm", ((Principal) user.getPrincipal()).getRealm());
+    }
   }
 
   @Get
@@ -409,11 +390,9 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
       getRequest().getAttributes().put("THROWN", t);
       throw t;
     } catch (final AuthenticationException e) {
-      getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN);
-      return null;
+      return error(ServerMessages.UNAUTHENTICATED, Status.CLIENT_ERROR_UNAUTHORIZED);
     } catch (final AuthorizationException e) {
-      getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN);
-      return null;
+      return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN);
     } catch (final Message m) {
       return error(m, Status.CLIENT_ERROR_BAD_REQUEST);
     } catch (final FileUploadException e) {
@@ -430,16 +409,8 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
     }
   }
 
-  public ArrayList<Integer> getRequestedIDs() {
-    return this.requestedIDs;
-  }
-
-  public ArrayList<String> getRequestedNames() {
-    return this.requestedNames;
-  }
-
-  public void setRequestedNames(final ArrayList<String> requestedNames) {
-    this.requestedNames = requestedNames;
+  public String[] getRequestedItems() {
+    return this.requestedItems;
   }
 
   public HashMap<String, String> getFlags() {
diff --git a/src/main/java/caosdb/server/resource/FileSystemResource.java b/src/main/java/caosdb/server/resource/FileSystemResource.java
index ecaa97fe93cb59fe6a4c58fb60d99a5dca373080..ef352e19eb178c5cefbdbd70d2e48b3231ac7bd7 100644
--- a/src/main/java/caosdb/server/resource/FileSystemResource.java
+++ b/src/main/java/caosdb/server/resource/FileSystemResource.java
@@ -135,8 +135,9 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
       try {
         getEntity(specifier).checkPermission(EntityPermission.RETRIEVE_FILE);
       } catch (EntityDoesNotExistException exception) {
-        // This file in the file system has no corresponding File record.
-        return error(ServerMessages.NOT_PERMITTED, Status.CLIENT_ERROR_FORBIDDEN);
+        // This file in the file system has no corresponding File record. It
+        // shall not be retrieved by anyone.
+        return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN);
       }
 
       final MediaType mt = MediaType.valueOf(FileUtils.getMimeType(file));
diff --git a/src/main/java/caosdb/server/resource/PermissionRulesResource.java b/src/main/java/caosdb/server/resource/PermissionRulesResource.java
index 1610ba372def0b758b76d15d6d839b825a9d96c6..dba1797a03287c503e5898c7e9a7dde6c389630c 100644
--- a/src/main/java/caosdb/server/resource/PermissionRulesResource.java
+++ b/src/main/java/caosdb/server/resource/PermissionRulesResource.java
@@ -46,7 +46,7 @@ public class PermissionRulesResource extends AbstractCaosDBServerResource {
   protected Representation httpGetInChildClass()
       throws ConnectionException, IOException, SQLException, CaosDBException,
           NoSuchAlgorithmException, Exception {
-    final String role = getRequestedNames().get(0);
+    final String role = getRequestedItems()[0];
 
     getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_PERMISSIONS(role));
 
@@ -73,7 +73,7 @@ public class PermissionRulesResource extends AbstractCaosDBServerResource {
   public Representation httpPutInChildClass(final Representation entity) throws Exception {
     final Element root = parseEntity(entity).getRootElement();
 
-    final String role = getRequestedNames().get(0);
+    final String role = getRequestedItems()[0];
     final HashSet<PermissionRule> rules = new HashSet<PermissionRule>();
 
     for (final Element e : root.getChildren()) {
diff --git a/src/main/java/caosdb/server/resource/RolesResource.java b/src/main/java/caosdb/server/resource/RolesResource.java
index ef9b30f5b831454fac392317e2c57833657c62d6..0f1b01e3230cf3e73f1c20949f38e180bd8f7e3b 100644
--- a/src/main/java/caosdb/server/resource/RolesResource.java
+++ b/src/main/java/caosdb/server/resource/RolesResource.java
@@ -52,8 +52,8 @@ public class RolesResource extends AbstractCaosDBServerResource {
     final Element root = generateRootElement();
     final Document document = new Document();
 
-    if (!getRequestedNames().isEmpty()) {
-      final String name = getRequestedNames().get(0);
+    if (getRequestedItems().length > 0) {
+      final String name = getRequestedItems()[0];
       if (name != null) {
         getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(name));
         final RetrieveRoleTransaction t = new RetrieveRoleTransaction(name);
@@ -78,8 +78,8 @@ public class RolesResource extends AbstractCaosDBServerResource {
   protected Representation httpDeleteInChildClass()
       throws ConnectionException, SQLException, CaosDBException, IOException,
           NoSuchAlgorithmException, Exception {
-    if (!getRequestedNames().isEmpty()) {
-      final String name = getRequestedNames().get(0);
+    if (getRequestedItems().length > 0) {
+      final String name = getRequestedItems()[0];
       if (name != null) {
         final DeleteRoleTransaction t = new DeleteRoleTransaction(name);
         try {
@@ -133,26 +133,28 @@ public class RolesResource extends AbstractCaosDBServerResource {
   protected Representation httpPutInChildClass(final Representation entity)
       throws ConnectionException, JDOMException, Exception, xmlNotWellFormedException {
 
-    final String name = getRequestedNames().get(0);
-    String description = null;
+    if (getRequestedItems().length > 0) {
+      final String name = getRequestedItems()[0];
+      String description = null;
 
-    final Form f = new Form(entity);
-    if (!f.isEmpty()) {
-      description = f.getFirstValue("role_description");
-    }
+      final Form f = new Form(entity);
+      if (!f.isEmpty()) {
+        description = f.getFirstValue("role_description");
+      }
 
-    if (name != null && description != null) {
-      final Role role = new Role();
-      role.name = name;
-      role.description = description;
-      final UpdateRoleTransaction t = new UpdateRoleTransaction(role);
-      try {
-        t.execute();
-      } catch (final Message m) {
-        if (m == ServerMessages.ROLE_DOES_NOT_EXIST) {
-          return error(m, Status.CLIENT_ERROR_NOT_FOUND);
-        } else {
-          throw m;
+      if (name != null && description != null) {
+        final Role role = new Role();
+        role.name = name;
+        role.description = description;
+        final UpdateRoleTransaction t = new UpdateRoleTransaction(role);
+        try {
+          t.execute();
+        } catch (final Message m) {
+          if (m == ServerMessages.ROLE_DOES_NOT_EXIST) {
+            return error(m, Status.CLIENT_ERROR_NOT_FOUND);
+          } else {
+            throw m;
+          }
         }
       }
     }
diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java
index b23a9593918ead75a6b38216838995e09ac67bc7..cb652b862282e2cf804ae4cbffd3b6f9de9d109f 100644
--- a/src/main/java/caosdb/server/resource/ScriptingResource.java
+++ b/src/main/java/caosdb/server/resource/ScriptingResource.java
@@ -25,12 +25,13 @@
 package caosdb.server.resource;
 
 import caosdb.server.FileSystem;
-import caosdb.server.accessControl.Principal;
+import caosdb.server.accessControl.AuthenticationUtils;
+import caosdb.server.accessControl.OneTimeAuthenticationToken;
 import caosdb.server.accessControl.SessionToken;
-import caosdb.server.accessControl.UserSources;
 import caosdb.server.entity.FileProperties;
 import caosdb.server.entity.Message;
 import caosdb.server.scripting.CallerSerializer;
+import caosdb.server.scripting.ScriptingPermissions;
 import caosdb.server.scripting.ServerSideScriptingCaller;
 import caosdb.server.utils.Serializer;
 import caosdb.server.utils.ServerMessages;
@@ -48,6 +49,7 @@ import org.apache.commons.fileupload.FileItemIterator;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.shiro.subject.Subject;
 import org.jdom2.Element;
 import org.restlet.data.CharacterSet;
 import org.restlet.data.Form;
@@ -83,9 +85,6 @@ public class ScriptingResource extends AbstractCaosDBServerResource {
   @Override
   protected Representation httpPostInChildClass(Representation entity) throws Exception {
 
-    if (isAnonymous()) {
-      return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN);
-    }
     MediaType mediaType = entity.getMediaType();
     try {
       if (mediaType.equals(MediaType.MULTIPART_FORM_DATA, true)) {
@@ -193,28 +192,42 @@ public class ScriptingResource extends AbstractCaosDBServerResource {
 
   public int callScript(Form form, List<FileProperties> files) throws Message {
     List<String> commandLine = form2CommandLine(form);
+    String call = commandLine.get(0);
+
+    checkExecutionPermission(getUser(), call);
     Integer timeoutMs = Integer.valueOf(form.getFirstValue("timeout", "-1"));
     return callScript(commandLine, timeoutMs, files);
   }
 
   public int callScript(List<String> commandLine, Integer timeoutMs, List<FileProperties> files)
       throws Message {
-    return callScript(commandLine, timeoutMs, files, generateAuthToken());
+    return callScript(commandLine, timeoutMs, files, generateAuthToken(commandLine.get(0)));
+  }
+
+  public boolean isAnonymous() {
+    return AuthenticationUtils.isAnonymous(getUser());
   }
 
-  public Object generateAuthToken() {
-    return SessionToken.generate((Principal) getUser().getPrincipal(), null);
+  /**
+   * Generate and return a token for the purpose of the given call. If the user is not anonymous and
+   * the call is not configured to be called by everyone, a SessionToken is returned instead.
+   */
+  public Object generateAuthToken(String call) {
+    String purpose = "SCRIPTING:EXECUTE:" + call;
+    Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser());
+    if (authtoken != null || isAnonymous()) {
+      return authtoken;
+    }
+    return SessionToken.generate(getUser());
   }
 
-  boolean isAnonymous() {
-    boolean ret = getUser().hasRole(UserSources.ANONYMOUS_ROLE);
-    return ret;
+  public void checkExecutionPermission(Subject user, String call) {
+    user.checkPermission(ScriptingPermissions.PERMISSION_EXECUTION(call));
   }
 
   public int callScript(
       List<String> commandLine, Integer timeoutMs, List<FileProperties> files, Object authToken)
       throws Message {
-    // TODO getUser().checkPermission("SCRIPTING:EXECUTE:" + commandLine.get(0));
     caller =
         new ServerSideScriptingCaller(
             commandLine.toArray(new String[commandLine.size()]), timeoutMs, files, authToken);
diff --git a/src/main/java/caosdb/server/resource/SharedFileResource.java b/src/main/java/caosdb/server/resource/SharedFileResource.java
index ffdce05baab5098d03080567ee815d286476f281..71cf089ddc4246196b8299502b1f15969183f372 100644
--- a/src/main/java/caosdb/server/resource/SharedFileResource.java
+++ b/src/main/java/caosdb/server/resource/SharedFileResource.java
@@ -71,7 +71,12 @@ public class SharedFileResource extends AbstractCaosDBServerResource {
 
     final MediaType mt = MediaType.valueOf(FileUtils.getMimeType(file));
     final FileRepresentation ret = new FileRepresentation(file, mt);
-    ret.setDisposition(new Disposition(Disposition.TYPE_ATTACHMENT));
+
+    // HTML files should be opened in the browser.
+    // Any other media type than HTML is attached for download.
+    if (!MediaType.TEXT_HTML.includes(mt)) {
+      ret.setDisposition(new Disposition(Disposition.TYPE_ATTACHMENT));
+    }
 
     return ret;
   }
diff --git a/src/main/java/caosdb/server/resource/UserResource.java b/src/main/java/caosdb/server/resource/UserResource.java
index b77ff8a49035d2be930a3f957e743ed2f0dae067..7dc9111ee332421fd27d1c32632cc458cbf6c800 100644
--- a/src/main/java/caosdb/server/resource/UserResource.java
+++ b/src/main/java/caosdb/server/resource/UserResource.java
@@ -60,9 +60,9 @@ public class UserResource extends AbstractCaosDBServerResource {
     final Document doc = new Document();
     final Element rootElem = generateRootElement();
 
-    if (!getRequestedNames().isEmpty()) {
+    if (getRequestedItems().length > 0) {
       try {
-        final String username = getRequestedNames().get(0);
+        final String username = getRequestedItems()[0];
         final String realm =
             (getRequestAttributes().containsKey("realm")
                 ? (String) getRequestAttributes().get("realm")
@@ -92,7 +92,7 @@ public class UserResource extends AbstractCaosDBServerResource {
 
     try {
       final Form form = new Form(entity);
-      final String username = getRequestedNames().get(0);
+      final String username = getRequestedItems()[0];
       final String realm =
           (getRequestAttributes().containsKey("realm")
               ? (String) getRequestAttributes().get("realm")
@@ -187,7 +187,8 @@ public class UserResource extends AbstractCaosDBServerResource {
     final Document doc = new Document();
     final Element rootElem = generateRootElement();
 
-    final DeleteUserTransaction t = new DeleteUserTransaction(getRequestedNames().get(0));
+    final String username = getRequestedItems()[0];
+    final DeleteUserTransaction t = new DeleteUserTransaction(username);
     try {
       t.execute();
     } catch (final Message m) {
diff --git a/src/main/java/caosdb/server/resource/UserRolesResource.java b/src/main/java/caosdb/server/resource/UserRolesResource.java
index 27e147157675982f036b88981b1dce221abae754..46d51ca4d60bec89b584cefc362e54ab37063503 100644
--- a/src/main/java/caosdb/server/resource/UserRolesResource.java
+++ b/src/main/java/caosdb/server/resource/UserRolesResource.java
@@ -47,7 +47,7 @@ public class UserRolesResource extends AbstractCaosDBServerResource {
   protected Representation httpGetInChildClass()
       throws ConnectionException, IOException, SQLException, CaosDBException,
           NoSuchAlgorithmException, Exception {
-    final String user = getRequestedNames().get(0);
+    final String user = getRequestedItems()[0];
     final String realm =
         (getRequestAttributes().get("realm") != null
             ? (String) getRequestAttributes().get("realm")
@@ -73,7 +73,7 @@ public class UserRolesResource extends AbstractCaosDBServerResource {
   @Override
   protected Representation httpPutInChildClass(final Representation entity)
       throws ConnectionException, JDOMException, Exception, xmlNotWellFormedException {
-    final String user = getRequestedNames().get(0);
+    final String user = getRequestedItems()[0];
     final String realm =
         (getRequestAttributes().get("realm") != null
             ? (String) getRequestAttributes().get("realm")
diff --git a/src/main/java/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/caosdb/server/resource/transaction/EntityResource.java
index dc25bcff3418b3f46f90e965a6b80cdf3111c07f..70e7bdf73adbf5e950fe0a51e119a8dbbe2e054f 100644
--- a/src/main/java/caosdb/server/resource/transaction/EntityResource.java
+++ b/src/main/java/caosdb/server/resource/transaction/EntityResource.java
@@ -30,8 +30,8 @@ import caosdb.server.entity.container.RetrieveContainer;
 import caosdb.server.entity.container.UpdateContainer;
 import caosdb.server.resource.AbstractCaosDBServerResource;
 import caosdb.server.resource.transaction.handlers.FileUploadHandler;
-import caosdb.server.resource.transaction.handlers.IDHandler;
 import caosdb.server.resource.transaction.handlers.RequestHandler;
+import caosdb.server.resource.transaction.handlers.SimpleDeleteRequestHandler;
 import caosdb.server.resource.transaction.handlers.SimpleGetRequestHandler;
 import caosdb.server.resource.transaction.handlers.SimpleWriteHandler;
 import caosdb.server.transaction.Delete;
@@ -72,7 +72,7 @@ public class EntityResource extends AbstractCaosDBServerResource {
   }
 
   protected RequestHandler<DeleteContainer> getDeleteRequestHandler() {
-    return new IDHandler<DeleteContainer>();
+    return new SimpleDeleteRequestHandler();
   }
 
   protected RequestHandler<InsertContainer> getPostRequestHandler() {
diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java
deleted file mode 100644
index e641ed416c86075d23a37d70ad791c26a820a966..0000000000000000000000000000000000000000
--- a/src/main/java/caosdb/server/resource/transaction/handlers/IDHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package caosdb.server.resource.transaction.handlers;
-
-import caosdb.server.entity.container.EntityByIdContainer;
-import caosdb.server.resource.transaction.EntityResource;
-
-public class IDHandler<T extends EntityByIdContainer> extends RequestHandler<T> {
-
-  @Override
-  public void handle(final EntityResource t, final T container) throws Exception {
-    for (final int id : t.getRequestedIDs()) {
-      container.add(id);
-    }
-  }
-}
diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fb416451bd8f053df5231eb1002db0c03544f99
--- /dev/null
+++ b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleDeleteRequestHandler.java
@@ -0,0 +1,55 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2018 Research Group Biomedical Physics,
+ *   Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.resource.transaction.handlers;
+
+import caosdb.server.entity.container.DeleteContainer;
+import caosdb.server.resource.transaction.EntityResource;
+
+public class SimpleDeleteRequestHandler extends RequestHandler<DeleteContainer> {
+
+  @Override
+  public void handle(final EntityResource t, final DeleteContainer container) throws Exception {
+    // TODO a lot of code duplication, see SimpleGetRequestHandle#handle.
+    // However, this is about to be changed again when string ids are
+    // introduced, anyways. So we just leave it.
+    for (final String item : t.getRequestedItems()) {
+      String[] elem = item.split("@", 1);
+      Integer id = null;
+      String version = null;
+      try {
+        id = Integer.parseInt(elem[0]);
+      } catch (NumberFormatException e) {
+        // pass
+      }
+      if (elem.length > 1) {
+        version = elem[1];
+      }
+
+      if (id != null) {
+        container.add(id, version);
+      }
+    }
+  }
+}
diff --git a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java
index 8fbb9d25cb33d2d0a75ce8405a164c1e8c9ce0fd..18c89d46966a3e22b1a9066b84dcf2d05efd122c 100644
--- a/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java
+++ b/src/main/java/caosdb/server/resource/transaction/handlers/SimpleGetRequestHandler.java
@@ -25,13 +25,29 @@ package caosdb.server.resource.transaction.handlers;
 import caosdb.server.entity.container.RetrieveContainer;
 import caosdb.server.resource.transaction.EntityResource;
 
-public class SimpleGetRequestHandler extends IDHandler<RetrieveContainer> {
+public class SimpleGetRequestHandler extends RequestHandler<RetrieveContainer> {
 
   @Override
   public void handle(final EntityResource t, final RetrieveContainer container) throws Exception {
-    super.handle(t, container);
-    for (final String name : t.getRequestedNames()) {
-      container.add(name);
+    for (final String item : t.getRequestedItems()) {
+      String[] elem = item.split("@", 2);
+      Integer id = null;
+      String name = null;
+      String version = null;
+      try {
+        id = Integer.parseInt(elem[0]);
+      } catch (NumberFormatException e) {
+        name = elem[0];
+      }
+      if (elem.length > 1) {
+        version = elem[1];
+      }
+
+      if (id != null) {
+        container.add(id, version);
+      } else {
+        container.add(name);
+      }
     }
   }
 }
diff --git a/src/main/java/caosdb/server/scripting/ScriptingPermissions.java b/src/main/java/caosdb/server/scripting/ScriptingPermissions.java
new file mode 100644
index 0000000000000000000000000000000000000000..9165f133c070c351e7a4ecbb2c510c06b71734a2
--- /dev/null
+++ b/src/main/java/caosdb/server/scripting/ScriptingPermissions.java
@@ -0,0 +1,11 @@
+package caosdb.server.scripting;
+
+public class ScriptingPermissions {
+
+  public static final String PERMISSION_EXECUTION(final String call) {
+    StringBuilder ret = new StringBuilder(18 + call.length());
+    ret.append("SCRIPTING:EXECUTE:");
+    ret.append(call.replace("/", ":"));
+    return ret.toString();
+  }
+}
diff --git a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java
index 6fa5d1a479df47c20d9d3a5d3a7134e1c0898ad1..faeb96e02738395647abb75fca3eaafe18040ad3 100644
--- a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java
+++ b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java
@@ -35,6 +35,7 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.ProcessBuilder.Redirect;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -165,8 +166,11 @@ public class ServerSideScriptingCaller {
       if (f.getPath() == null || f.getPath().isEmpty()) {
         throw new CaosDBException("The path must not be null or empty!");
       }
-      caosdb.server.utils.FileUtils.createSymlink(
-          getUploadFilesDir().toPath().resolve(f.getPath()).toFile(), f.getFile());
+      File link = getUploadFilesDir().toPath().resolve(f.getPath()).toFile();
+      if (!link.getParentFile().exists()) {
+        link.getParentFile().mkdirs();
+      }
+      caosdb.server.utils.FileUtils.createSymlink(link, f.getFile());
     }
   }
 
@@ -255,7 +259,12 @@ public class ServerSideScriptingCaller {
   }
 
   int callScript() throws IOException, InterruptedException, TimeoutException {
-    String[] effectiveCommandLine = injectAuthToken(getCommandLine());
+    String[] effectiveCommandLine;
+    if (authToken != null) {
+      effectiveCommandLine = injectAuthToken(getCommandLine());
+    } else {
+      effectiveCommandLine = Arrays.copyOf(getCommandLine(), getCommandLine().length);
+    }
     effectiveCommandLine[0] = makeCallAbsolute(effectiveCommandLine[0]);
     final ProcessBuilder pb = new ProcessBuilder(effectiveCommandLine);
 
diff --git a/src/main/java/caosdb/server/terminal/CaosDBTerminal.java b/src/main/java/caosdb/server/terminal/CaosDBTerminal.java
index 8113d4e1e7c36adb517543b778d03ec9a0970598..c85495539d93d67af26b1b1ef4b62a35dd57ff48 100644
--- a/src/main/java/caosdb/server/terminal/CaosDBTerminal.java
+++ b/src/main/java/caosdb/server/terminal/CaosDBTerminal.java
@@ -30,6 +30,11 @@ import com.googlecode.lanterna.terminal.Terminal;
 import com.googlecode.lanterna.terminal.text.UnixTerminal;
 import java.nio.charset.Charset;
 
+/**
+ * @deprecated Soon to be removed
+ * @author Timm Fitschen (t.fitschen@indiscale.com)
+ */
+@Deprecated
 public class CaosDBTerminal extends Thread {
 
   public CaosDBTerminal() {
diff --git a/src/main/java/caosdb/server/transaction/ChecksumUpdater.java b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java
index 30313478c263a131658005daa39da3b495c85eb2..ec923bec7bf3368492f28bdafa3f11b5e7bd4ec9 100644
--- a/src/main/java/caosdb/server/transaction/ChecksumUpdater.java
+++ b/src/main/java/caosdb/server/transaction/ChecksumUpdater.java
@@ -27,7 +27,7 @@ import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.GetUpdateableChecksums;
 import caosdb.server.database.backend.transaction.RetrieveSparseEntity;
-import caosdb.server.database.backend.transaction.UpdateSparseEntity;
+import caosdb.server.database.backend.transaction.SetFileChecksum;
 import caosdb.server.database.exceptions.TransactionException;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.FileProperties;
@@ -46,6 +46,7 @@ import java.security.NoSuchAlgorithmException;
 public class ChecksumUpdater extends WriteTransaction<TransactionContainer> implements Runnable {
 
   private Boolean running = false;
+
   private static final ChecksumUpdater instance = new ChecksumUpdater();
 
   private ChecksumUpdater() {
@@ -85,7 +86,7 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
           DatabaseMonitor.getInstance().acquireStrongAccess(this);
 
           // update
-          execute(new UpdateSparseEntity(fileEntity), strongAccess);
+          execute(new SetFileChecksum(fileEntity), strongAccess);
           strongAccess.commit();
 
         } catch (final InterruptedException e) {
@@ -141,10 +142,11 @@ public class ChecksumUpdater extends WriteTransaction<TransactionContainer> impl
           instance.running = false;
           return null;
         }
-        return execute(new RetrieveSparseEntity(id), weakAccess).getEntity();
+        return execute(new RetrieveSparseEntity(id, null), weakAccess).getEntity();
       }
     } catch (final Exception e) {
       e.printStackTrace();
+      instance.running = false;
       return null;
     } finally {
       weakAccess.release();
diff --git a/src/main/java/caosdb/server/transaction/Insert.java b/src/main/java/caosdb/server/transaction/Insert.java
index 627ada52655c5e7802e98172d1c32f8280a95fb4..c1d53f3051eb65efd9d658179e514e13863c313b 100644
--- a/src/main/java/caosdb/server/transaction/Insert.java
+++ b/src/main/java/caosdb/server/transaction/Insert.java
@@ -26,6 +26,7 @@ import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.transaction.InsertEntity;
 import caosdb.server.entity.EntityInterface;
 import caosdb.server.entity.FileProperties;
+import caosdb.server.entity.Version;
 import caosdb.server.entity.container.InsertContainer;
 import caosdb.server.entity.container.TransactionContainer;
 import caosdb.server.permissions.EntityACL;
@@ -103,6 +104,10 @@ public class Insert extends WriteTransaction<InsertContainer> {
   public void insert(final TransactionContainer container, final Access access) throws Exception {
     if (container.getStatus().ordinal() >= EntityStatus.QUALIFIED.ordinal()) {
       execute(new InsertEntity(container), access);
+      for (EntityInterface e : container) {
+        // TODO move to InsertEntity transaction
+        e.setVersion(new Version(e.getVersion().getId(), this.getTimestamp()));
+      }
     }
   }
 
diff --git a/src/main/java/caosdb/server/transaction/RetrieveUserRolesTransaction.java b/src/main/java/caosdb/server/transaction/RetrieveUserRolesTransaction.java
index 76740a73c9439e4ea9e45479e57501c4c4e1a0d0..e864c20bcf040faf78dc54e24dbcdefc1a8be795 100644
--- a/src/main/java/caosdb/server/transaction/RetrieveUserRolesTransaction.java
+++ b/src/main/java/caosdb/server/transaction/RetrieveUserRolesTransaction.java
@@ -58,7 +58,7 @@ public class RetrieveUserRolesTransaction implements TransactionInterface {
   @Override
   public void execute() throws Exception {
     if (UserSources.isUserExisting(new Principal(this.realm, this.user))) {
-      this.roles = UserSources.resolve(this.realm, this.user);
+      this.roles = UserSources.resolveRoles(this.realm, this.user);
     } else {
       throw ServerMessages.ACCOUNT_DOES_NOT_EXIST;
     }
diff --git a/src/main/java/caosdb/server/transaction/Transaction.java b/src/main/java/caosdb/server/transaction/Transaction.java
index 28b8276e2b93699f2315fb754303194208bb37e5..d2c16438b5c071fec72b11b600604b46c4b2da30 100644
--- a/src/main/java/caosdb/server/transaction/Transaction.java
+++ b/src/main/java/caosdb/server/transaction/Transaction.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,7 +25,6 @@
 package caosdb.server.transaction;
 
 import caosdb.datetime.UTCDateTime;
-import caosdb.server.accessControl.AuthenticationUtils;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
@@ -227,14 +228,8 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra
   // TODO move to post-transaction job
   private void writeHistory() throws TransactionException, Message {
     if (logHistory()) {
-      String realm =
-          getTransactor().getPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()
-              ? ""
-              : ((Principal) getTransactor().getPrincipal()).getRealm();
-      String username =
-          getTransactor().getPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()
-              ? "anonymous"
-              : ((Principal) getTransactor().getPrincipal()).getUsername();
+      String realm = ((Principal) getTransactor().getPrincipal()).getRealm();
+      String username = ((Principal) getTransactor().getPrincipal()).getUsername();
       execute(
           new InsertTransactionHistory(
               getContainer(), this.getClass().getSimpleName(), realm, username, getTimestamp()),
diff --git a/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java b/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
index 257e8c06cc9d9c9f24774e541cea611663f5ff0d..f11400c3826170de658fd9e5a634665f00e1cb44 100644
--- a/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
+++ b/src/main/java/caosdb/server/transaction/UpdateUserRolesTransaction.java
@@ -68,7 +68,7 @@ public class UpdateUserRolesTransaction extends AccessControlTransaction {
   }
 
   public Element getUserRolesElement() {
-    final Set<String> resulting_roles = UserSources.resolve(this.realm, this.user);
+    final Set<String> resulting_roles = UserSources.resolveRoles(this.realm, this.user);
     final Element rolesElem = RetrieveUserRolesTransaction.getUserRolesElement(resulting_roles);
     if (!this.roles.equals(resulting_roles) && resulting_roles != null) {
       final Element warning = new Element("Warning");
diff --git a/src/main/java/caosdb/server/transaction/WriteTransaction.java b/src/main/java/caosdb/server/transaction/WriteTransaction.java
index e67ae0600bc573efbdf67b9b341299fe1fe3ca00..ec37d19d6f5a4663c0af0adda186c282215a302b 100644
--- a/src/main/java/caosdb/server/transaction/WriteTransaction.java
+++ b/src/main/java/caosdb/server/transaction/WriteTransaction.java
@@ -64,4 +64,8 @@ public abstract class WriteTransaction<C extends TransactionContainer> extends T
       }
     }
   }
+
+  public String getSRID() {
+    return getContainer().getRequestId();
+  }
 }
diff --git a/src/main/java/caosdb/server/utils/FileUtils.java b/src/main/java/caosdb/server/utils/FileUtils.java
index 824646caab6983863acd7d35a276a1213b393235..2357b7217c0fa9b112dd6c9e45d5be64d36edefb 100644
--- a/src/main/java/caosdb/server/utils/FileUtils.java
+++ b/src/main/java/caosdb/server/utils/FileUtils.java
@@ -218,6 +218,13 @@ public class FileUtils {
     }
   }
 
+  /**
+   * @deprecated Soon to be removed.
+   * @throws IOException
+   * @throws InterruptedException
+   * @throws CaosDBException
+   */
+  @Deprecated
   public static void testChownScript() throws IOException, InterruptedException, CaosDBException {
     final String sudopw = CaosDBServer.getServerProperty(ServerProperties.KEY_SUDO_PASSWORD);
     final Process cmd =
diff --git a/src/main/java/caosdb/server/utils/Initialization.java b/src/main/java/caosdb/server/utils/Initialization.java
index e77922a417c1a2ef9542cb3dc9b8971ca10b332a..9f77fcf2992c0e7fb8de38ee334bebe8c93ef3d8 100644
--- a/src/main/java/caosdb/server/utils/Initialization.java
+++ b/src/main/java/caosdb/server/utils/Initialization.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -26,7 +28,7 @@ import caosdb.server.database.DatabaseMonitor;
 import caosdb.server.database.access.Access;
 import caosdb.server.transaction.TransactionInterface;
 
-public final class Initialization implements TransactionInterface {
+public final class Initialization implements TransactionInterface, AutoCloseable {
 
   private Access access;
   private static final Initialization instance = new Initialization();
@@ -43,13 +45,14 @@ public final class Initialization implements TransactionInterface {
     return this.access;
   }
 
-  public final void release() {
+  @Override
+  public void execute() throws Exception {}
+
+  @Override
+  public void close() throws Exception {
     if (this.access != null) {
       this.access.release();
       this.access = null;
     }
   }
-
-  @Override
-  public void execute() throws Exception {}
 }
diff --git a/src/main/java/caosdb/server/utils/ServerMessages.java b/src/main/java/caosdb/server/utils/ServerMessages.java
index c2f3a302d270a3f16616c00b41ec9118862d4605..29983924246f5bc5987ffdbfeaf949d0946c7f16 100644
--- a/src/main/java/caosdb/server/utils/ServerMessages.java
+++ b/src/main/java/caosdb/server/utils/ServerMessages.java
@@ -133,8 +133,6 @@ public class ServerMessages {
           0,
           "Cannot parse value to boolean (either 'true' or 'false', ignoring case).");
 
-  public static final Message NOT_PERMITTED = new Message(MessageType.Error, 0, "Not permitted.");
-
   public static final Message CANNOT_CONNECT_TO_DATABASE =
       new Message(MessageType.Error, 0, "Could not connect to MySQL server.");
 
@@ -257,6 +255,9 @@ public class ServerMessages {
           0,
           "User has been activated. You can now log in with your username and password.");
 
+  public static final Message UNAUTHENTICATED =
+      new Message(MessageType.Error, 401, "Sign in, please.");
+
   public static final Message AUTHORIZATION_ERROR =
       new Message(MessageType.Error, 403, "You are not allowed to do this.");
 
diff --git a/src/main/java/caosdb/server/utils/Utils.java b/src/main/java/caosdb/server/utils/Utils.java
index 6c48906a40fd299379e91446516a9c0325200f81..752d828d0e89e790aa7d42e1358b91d3fb5f3c1b 100644
--- a/src/main/java/caosdb/server/utils/Utils.java
+++ b/src/main/java/caosdb/server/utils/Utils.java
@@ -33,7 +33,6 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.text.DecimalFormat;
-import java.util.Random;
 import java.util.Scanner;
 import java.util.regex.Pattern;
 import org.apache.commons.codec.binary.Base32;
@@ -45,30 +44,9 @@ import org.jdom2.output.XMLOutputter;
 /** Utility functions. */
 public class Utils {
 
-  /** Random number generator initialized with the system time and used for generating UIDs. */
-  private static Random rand = new Random(System.currentTimeMillis());
-
   /** Secure random number generator, for secret random numbers. */
   private static final SecureRandom srand = new SecureRandom();
 
-  /**
-   * Check whether obj is non-null and can be parsed to an integer.
-   *
-   * @param obj The object to check.
-   * @return true if obj is not null and obj.toString() can be parsed as an integer
-   */
-  public static boolean isNonNullInteger(final Object obj) {
-    if (obj == null) {
-      return false;
-    }
-    try {
-      Integer.parseInt(obj.toString());
-    } catch (final NumberFormatException e) {
-      return false;
-    }
-    return true;
-  }
-
   /**
    * Regular expression pattern that checks a mail for RFC822 compliance.
    *
@@ -185,16 +163,14 @@ public class Utils {
    * @return The UID as a String.
    */
   public static String getUID() {
-    synchronized (rand) {
-      return Long.toHexString(rand.nextLong()) + Long.toHexString(rand.nextLong());
-    }
+    return Long.toHexString(srand.nextLong()) + Long.toHexString(srand.nextLong());
   }
 
   /**
    * Generate a secure filename (base32 letters and numbers).
    *
-   * <p>Very similar to getUID, but uses cryptographic random number instead, also also nicely
-   * formats the resulting string.
+   * <p>Very similar to getUID, but uses cryptographic random number instead, also nicely formats
+   * the resulting string.
    *
    * @param byteSize How many bytes of random bits shall be generated.
    * @return The filename as a String.
@@ -205,6 +181,7 @@ public class Utils {
     srand.nextBytes(bytes);
 
     // Encode to nice letters
+    // TODO use StringBuilder and iterate over filename.charArray
     String filename = (new Base32()).encodeToString(bytes);
     filename = filename.replaceAll("=+", "");
 
diff --git a/src/test/java/caosdb/server/Misc.java b/src/test/java/caosdb/server/Misc.java
index bcdc1729f008dbb1393fe238a3eb763c67d69e0f..2988bae6aa178ace45f41e03da5c54f08114e485 100644
--- a/src/test/java/caosdb/server/Misc.java
+++ b/src/test/java/caosdb/server/Misc.java
@@ -39,11 +39,7 @@ import java.io.ObjectOutputStream;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.config.Ini;
-import org.apache.shiro.config.IniSecurityManagerFactory;
-import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
-import org.apache.shiro.util.Factory;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -293,12 +289,7 @@ public class Misc {
 
   @Test
   public void testShiro() {
-    Ini config = CaosDBServer.getShiroConfig();
-    final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config);
-
-    final SecurityManager securityManager = factory.getInstance();
-
-    SecurityUtils.setSecurityManager(securityManager);
+    CaosDBServer.initShiro();
 
     final Subject subject = SecurityUtils.getSubject();
 
diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java
index 9937dc60b1a33cc421a74661bed028ae5e24522a..9f344889051d8508125dc5f6417d40839c44bd3d 100644
--- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java
+++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java
@@ -22,13 +22,43 @@
  */
 package caosdb.server.authentication;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
 import caosdb.server.CaosDBServer;
+import caosdb.server.ServerProperties;
+import caosdb.server.accessControl.AnonymousAuthenticationToken;
 import caosdb.server.accessControl.AuthenticationUtils;
+import caosdb.server.accessControl.Config;
 import caosdb.server.accessControl.OneTimeAuthenticationToken;
 import caosdb.server.accessControl.Principal;
+import caosdb.server.accessControl.SelfValidatingAuthenticationToken;
 import caosdb.server.accessControl.SessionToken;
+import caosdb.server.accessControl.SessionTokenRealm;
+import caosdb.server.database.BackendTransaction;
+import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl;
+import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl;
+import caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
+import caosdb.server.database.backend.interfaces.RetrieveUserImpl;
+import caosdb.server.resource.TestScriptingResource.RetrievePasswordValidator;
+import caosdb.server.resource.TestScriptingResource.RetrievePermissionRules;
+import caosdb.server.resource.TestScriptingResource.RetrieveRole;
+import caosdb.server.resource.TestScriptingResource.RetrieveUser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.io.input.CharSequenceInputStream;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.subject.Subject;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -39,62 +69,49 @@ public class AuthTokenTest {
     CaosDBServer.initServerProperties();
   }
 
+  @BeforeClass
+  public static void setupShiro() throws IOException {
+    BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRole.class);
+    BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class);
+    BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUser.class);
+    BackendTransaction.setImpl(
+        RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class);
+
+    CaosDBServer.initServerProperties();
+    CaosDBServer.initShiro();
+  }
+
+  @Before
+  public void reset() {
+    OneTimeAuthenticationToken.resetConfig();
+  }
+
   @Test
   public void testSessionToken() throws InterruptedException {
-    final String curry = "somecurry";
+    // Token 1 - wrong checksum, not expired
     final SessionToken t1 =
         new SessionToken(
             new Principal("somerealm", "someuser1"),
             System.currentTimeMillis(),
             60000,
             "345sdf56sdf",
-            curry,
-            "wrong checksum");
+            "wrong checksum",
+            null,
+            null);
     Assert.assertFalse(t1.isExpired());
     Assert.assertFalse(t1.isHashValid());
     Assert.assertFalse(t1.isValid());
 
-    final SessionToken t2 =
-        new SessionToken(
-            new Principal("somerealm", "someuser1"),
-            System.currentTimeMillis(),
-            60000,
-            "345sdf56sdf",
-            null,
-            "wrong checksum");
-    Assert.assertFalse(t2.isExpired());
-    Assert.assertFalse(t2.isHashValid());
-    Assert.assertFalse(t2.isValid());
-
+    // Token 3 - correct checksum, not expired
     final SessionToken t3 =
-        new SessionToken(
-            new Principal("somerealm", "someuser2"),
-            System.currentTimeMillis(),
-            60000,
-            "72723gsdg",
-            curry);
+        new SessionToken(new Principal("somerealm", "someuser2"), 60000, null, null);
     Assert.assertFalse(t3.isExpired());
     Assert.assertTrue(t3.isHashValid());
     Assert.assertTrue(t3.isValid());
 
-    final SessionToken t4 =
-        new SessionToken(
-            new Principal("somerealm", "someuser2"),
-            System.currentTimeMillis(),
-            60000,
-            "72723gsdg",
-            null);
-    Assert.assertFalse(t4.isExpired());
-    Assert.assertTrue(t4.isHashValid());
-    Assert.assertTrue(t4.isValid());
-
+    // Token 5 - correct checksum, soon to be expired
     final SessionToken t5 =
-        new SessionToken(
-            new Principal("somerealm", "someuser3"),
-            System.currentTimeMillis(),
-            2000,
-            "23sdfsg34",
-            curry);
+        new SessionToken(new Principal("somerealm", "someuser3"), 2000, null, null);
     Assert.assertFalse(t5.isExpired());
     Assert.assertTrue(t5.isHashValid());
     Assert.assertTrue(t5.isValid());
@@ -104,113 +121,331 @@ public class AuthTokenTest {
     Assert.assertTrue(t5.isHashValid());
     Assert.assertFalse(t5.isValid());
 
+    // Token 6 - correct checksum, immediately expired
     final SessionToken t6 =
-        new SessionToken(
-            new Principal("somerealm", "someuser3"),
-            System.currentTimeMillis(),
-            0,
-            "23sdfsg34",
-            null);
+        new SessionToken(new Principal("somerealm", "someuser3"), 0, null, null);
     Assert.assertTrue(t6.isExpired());
     Assert.assertTrue(t6.isHashValid());
     Assert.assertFalse(t6.isValid());
 
-    Assert.assertEquals(t1.toString(), SessionToken.parse(t1.toString(), curry).toString());
-    Assert.assertEquals(t2.toString(), SessionToken.parse(t2.toString(), curry).toString());
-    Assert.assertEquals(t3.toString(), SessionToken.parse(t3.toString(), curry).toString());
-    Assert.assertEquals(t4.toString(), SessionToken.parse(t4.toString(), curry).toString());
-    Assert.assertEquals(t5.toString(), SessionToken.parse(t5.toString(), curry).toString());
-    Assert.assertEquals(t6.toString(), SessionToken.parse(t6.toString(), curry).toString());
-    Assert.assertEquals(t1.toString(), SessionToken.parse(t1.toString(), null).toString());
-    Assert.assertEquals(t2.toString(), SessionToken.parse(t2.toString(), null).toString());
-    Assert.assertEquals(t3.toString(), SessionToken.parse(t3.toString(), null).toString());
-    Assert.assertEquals(t4.toString(), SessionToken.parse(t4.toString(), null).toString());
-    Assert.assertEquals(t5.toString(), SessionToken.parse(t5.toString(), null).toString());
-    Assert.assertEquals(t6.toString(), SessionToken.parse(t6.toString(), null).toString());
-
-    Assert.assertFalse(SessionToken.parse(t1.toString(), curry).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t2.toString(), null).isHashValid());
-    Assert.assertTrue(SessionToken.parse(t3.toString(), curry).isHashValid());
-    Assert.assertTrue(SessionToken.parse(t4.toString(), null).isHashValid());
-    Assert.assertTrue(SessionToken.parse(t5.toString(), curry).isHashValid());
-    Assert.assertTrue(SessionToken.parse(t6.toString(), null).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t1.toString(), null).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t2.toString(), curry).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t3.toString(), null).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t4.toString(), curry).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t5.toString(), null).isHashValid());
-    Assert.assertFalse(SessionToken.parse(t6.toString(), curry).isHashValid());
+    // All tokens can be successfully parsed back.
+    final SelfValidatingAuthenticationToken t1p = SessionToken.parse(t1.toString());
+    final SelfValidatingAuthenticationToken t3p = SessionToken.parse(t3.toString());
+    final SelfValidatingAuthenticationToken t5p = SessionToken.parse(t5.toString());
+    final SelfValidatingAuthenticationToken t6p = SessionToken.parse(t6.toString());
+    Assert.assertEquals(t1.toString(), t1p.toString());
+    Assert.assertEquals(t3.toString(), t3p.toString());
+    Assert.assertEquals(t5.toString(), t5p.toString());
+    Assert.assertEquals(t6.toString(), t6p.toString());
+
+    // ... and parsed tokens have the correct hash validation
+    Assert.assertFalse(t1p.isHashValid());
+    Assert.assertTrue(t3p.isHashValid());
+    Assert.assertTrue(t5p.isHashValid());
+    Assert.assertTrue(t6p.isHashValid());
 
     Assert.assertFalse(
         AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), null)
-            .isExpired());
-    Assert.assertTrue(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), null)
-            .isHashValid());
-    Assert.assertTrue(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), null)
-            .isValid());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), curry)
-            .isExpired());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), curry)
-            .isHashValid());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t4), curry)
-            .isValid());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), null)
-            .isExpired());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), null)
-            .isHashValid());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), null)
-            .isValid());
-    Assert.assertFalse(
-        AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), curry)
+                AuthenticationUtils.createSessionTokenCookie(t3))
             .isExpired());
     Assert.assertTrue(
         AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), curry)
+                AuthenticationUtils.createSessionTokenCookie(t3))
             .isHashValid());
     Assert.assertTrue(
         AuthenticationUtils.parseSessionTokenCookie(
-                AuthenticationUtils.createSessionTokenCookie(t3), curry)
+                AuthenticationUtils.createSessionTokenCookie(t3))
             .isValid());
+
+    // TODO parse invalid tokens
   }
 
   @Test
-  public void testOneTimeToken() {
-    final String curry = null;
+  public void testOneTimeTokenSerialization() {
     final OneTimeAuthenticationToken t1 =
         new OneTimeAuthenticationToken(
             new Principal("somerealm", "someuser"),
-            System.currentTimeMillis(),
-            60000L,
-            "sdfh37456sd",
-            curry,
-            new String[] {""});
-    System.err.println(t1.toString());
+            60000,
+            new String[] {"permissions"},
+            new String[] {"roles"},
+            1L,
+            3000L);
+    Assert.assertEquals(1L, t1.getMaxReplays());
     Assert.assertFalse(t1.isExpired());
     Assert.assertTrue(t1.isHashValid());
     Assert.assertTrue(t1.isValid());
 
+    String serialized = t1.toString();
+    OneTimeAuthenticationToken parsed =
+        (OneTimeAuthenticationToken) OneTimeAuthenticationToken.parse(serialized);
+
+    Assert.assertEquals(t1, parsed);
+    Assert.assertEquals(serialized, parsed.toString());
+
+    Assert.assertEquals(1L, parsed.getMaxReplays());
+    Assert.assertFalse(parsed.isExpired());
+    Assert.assertTrue(parsed.isHashValid());
+    Assert.assertTrue(parsed.isValid());
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testOneTimeTokenConsume() {
+    final OneTimeAuthenticationToken t1 =
+        new OneTimeAuthenticationToken(
+            new Principal("somerealm", "someuser"),
+            60000,
+            new String[] {"permissions"},
+            new String[] {"roles"},
+            3L,
+            3000L);
+    Assert.assertFalse(t1.isExpired());
+    Assert.assertTrue(t1.isHashValid());
+    Assert.assertTrue(t1.isValid());
+    try {
+      t1.consume();
+      t1.consume();
+      t1.consume();
+    } catch (AuthenticationException e) {
+      Assert.fail(e.getMessage());
+    }
+
+    // throws
+    t1.consume();
+    Assert.fail("4th time consume() should throw");
+  }
+
+  @Test(expected = AuthenticationException.class)
+  public void testOneTimeTokenConsumeByParsing() {
+    final OneTimeAuthenticationToken t1 =
+        new OneTimeAuthenticationToken(
+            new Principal("somerealm", "someuser"),
+            60000,
+            new String[] {"permissions"},
+            new String[] {"roles"},
+            3L,
+            3000L);
+    Assert.assertFalse(t1.isExpired());
+    Assert.assertTrue(t1.isHashValid());
+    Assert.assertTrue(t1.isValid());
+
+    String serialized = t1.toString();
+    try {
+      SelfValidatingAuthenticationToken parsed1 = OneTimeAuthenticationToken.parse(serialized);
+      Assert.assertTrue(parsed1.isValid());
+      SelfValidatingAuthenticationToken parsed2 = OneTimeAuthenticationToken.parse(serialized);
+      Assert.assertTrue(parsed2.isValid());
+      SelfValidatingAuthenticationToken parsed3 = OneTimeAuthenticationToken.parse(serialized);
+      Assert.assertTrue(parsed3.isValid());
+    } catch (AuthenticationException e) {
+      Assert.fail(e.getMessage());
+    }
+
+    // throws
+    OneTimeAuthenticationToken.parse(serialized);
+    Assert.fail("4th parsing should throw");
+  }
+
+  @Test
+  public void testOneTimeTokenConfigEmpty() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("[]");
+
+    List<Config> configs =
+        OneTimeAuthenticationToken.loadConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+
+    Assert.assertTrue("empty config", configs.isEmpty());
+  }
+
+  @Test
+  public void testOneTimeTokenConfigDefaults() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("roles: []\n");
+
+    List<Config> configs =
+        OneTimeAuthenticationToken.loadConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+
+    Assert.assertEquals(1, configs.size());
+    Assert.assertTrue("parsing to Config object", configs.get(0) instanceof Config);
+
+    Config config = configs.get(0);
+
     Assert.assertEquals(
-        t1.toString(), OneTimeAuthenticationToken.parse(t1.toString(), curry).toString());
-    Assert.assertFalse(OneTimeAuthenticationToken.parse(t1.toString(), curry).isExpired());
-    Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isHashValid());
-    Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isValid());
+        Integer.parseInt(
+            CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)),
+        config.getExpiresAfter());
+    Assert.assertEquals(1, config.getMaxReplays());
+    Assert.assertNull("no purpose", config.getPurpose());
+
+    Assert.assertArrayEquals("no permissions", new String[] {}, config.getPermissions());
+    Assert.assertArrayEquals("no roles", new String[] {}, config.getRoles());
+    Assert.assertNull("no output", config.getOutput());
+  }
+
+  @Test
+  public void testOneTimeTokenConfigBasic() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("purpose: test purpose 1\n");
+    testYaml.append("roles: [ role1, \"role2\"]\n");
+    testYaml.append("expiresAfterSeconds: 10\n");
+    testYaml.append("maxReplays: 3\n");
+    testYaml.append("permissions:\n");
+    testYaml.append("  - permission1\n");
+    testYaml.append("  - 'permission2'\n");
+    testYaml.append("  - \"permission3\"\n");
+    testYaml.append("  - \"permission with white space\"\n");
+
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+    Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap();
+    Assert.assertNotNull("test purpose there", map.get("test purpose 1"));
+    Assert.assertTrue("parsing to Config object", map.get("test purpose 1") instanceof Config);
+    Config config = map.get("test purpose 1");
+    Assert.assertEquals(10000, config.getExpiresAfter());
+    Assert.assertEquals(3, config.getMaxReplays());
+
+    Assert.assertArrayEquals(
+        "permissions parsed",
+        new String[] {"permission1", "permission2", "permission3", "permission with white space"},
+        config.getPermissions());
+    Assert.assertArrayEquals("roles parsed", new String[] {"role1", "role2"}, config.getRoles());
+  }
+
+  @Test
+  public void testOneTimeTokenConfigNoRoles() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("purpose: no roles test\n");
+    testYaml.append("permissions:\n");
+    testYaml.append("  - permission1\n");
+    testYaml.append("  - 'permission2'\n");
+    testYaml.append("  - \"permission3\"\n");
+    testYaml.append("  - \"permission with white space\"\n");
+
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+    Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap();
+    Config config = map.get("no roles test");
+
+    Assert.assertArrayEquals("empty roles array parsed", new String[] {}, config.getRoles());
+  }
+
+  @Test
+  public void testOneTimeTokenConfigNoPurpose() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("permissions:\n");
+    testYaml.append("  - permission1\n");
+    testYaml.append("  - 'permission2'\n");
+    testYaml.append("  - \"permission3\"\n");
+    testYaml.append("  - \"permission with white space\"\n");
+
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+    Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap();
+    Assert.assertEquals(map.size(), 0);
+  }
+
+  @Test
+  public void testOneTimeTokenConfigMulti() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("- purpose: purpose 1\n");
+    testYaml.append("- purpose: purpose 2\n");
+    testYaml.append("- purpose: purpose 3\n");
+    testYaml.append("- purpose: purpose 4\n");
+
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+    Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap();
+    Assert.assertEquals("four items", 4, map.size());
+    Assert.assertTrue(map.get("purpose 2") instanceof Config);
+  }
+
+  @Test
+  public void testOneTimeTokenConfigOutputFile() throws Exception {
+    File tempFile = File.createTempFile("authtoken", "json");
+    tempFile.deleteOnExit();
+
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("- output:\n");
+    testYaml.append("    file: " + tempFile.getAbsolutePath() + "\n");
+    testYaml.append("  permissions: [ permission1 ]\n");
+
+    // write the token
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+    Assert.assertTrue(tempFile.exists());
+    try (BufferedReader reader = new BufferedReader(new FileReader(tempFile))) {
+      OneTimeAuthenticationToken token =
+          (OneTimeAuthenticationToken) SelfValidatingAuthenticationToken.parse(reader.readLine());
+      assertEquals("Token has anonymous username", "anonymous", token.getPrincipal().getUsername());
+      assertEquals(
+          "Token has anonymous realm",
+          OneTimeAuthenticationToken.REALM_NAME,
+          token.getPrincipal().getRealm());
+      assertArrayEquals(
+          "Permissions array has been written and read",
+          new String[] {"permission1"},
+          token.getPermissions().toArray());
+    }
+  }
+
+  @Test
+  public void testOneTimeTokenForAnonymous() throws Exception {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("purpose: for anonymous\n");
+    testYaml.append("roles: [ role1 ]\n");
+    testYaml.append("permissions:\n");
+    testYaml.append("  - permission1\n");
+
+    OneTimeAuthenticationToken.initConfig(new CharSequenceInputStream(testYaml, "utf-8"));
+
+    Subject anonymous = SecurityUtils.getSubject();
+    anonymous.login(AnonymousAuthenticationToken.getInstance());
+
+    OneTimeAuthenticationToken token =
+        OneTimeAuthenticationToken.generateForPurpose("for anonymous", anonymous);
+    assertEquals("anonymous", token.getPrincipal().getUsername());
+  }
+
+  @Test
+  public void testSessionTokenRealm() {
+    Config config = new Config();
+    OneTimeAuthenticationToken token = OneTimeAuthenticationToken.generate(config);
+
+    String serialized = token.toString();
+    SelfValidatingAuthenticationToken parsed = SelfValidatingAuthenticationToken.parse(serialized);
+
+    SessionTokenRealm sessionTokenRealm = new SessionTokenRealm();
+    Assert.assertTrue(sessionTokenRealm.supports(token));
+    Assert.assertTrue(sessionTokenRealm.supports(parsed));
+
+    Assert.assertNotNull(sessionTokenRealm.getAuthenticationInfo(token));
+    Assert.assertNotNull(sessionTokenRealm.getAuthenticationInfo(parsed));
+
+    Subject anonymous = SecurityUtils.getSubject();
+    anonymous.login(token);
+  }
+
+  @Test
+  public void testIntInConfigYaml() throws IOException {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("- expiresAfterSeconds: 1000\n");
+    testYaml.append("  replayTimeout: 1000\n");
+
+    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+    ObjectReader reader = mapper.readerFor(Config.class);
+    Config config =
+        (Config) reader.readValues(new CharSequenceInputStream(testYaml, "utf-8")).next();
+
+    assertEquals(1000000, config.getExpiresAfter());
+    assertEquals(1000, config.getReplayTimeout());
+  }
+
+  @Test
+  public void testLongInConfigYaml() throws IOException {
+    StringBuilder testYaml = new StringBuilder();
+    testYaml.append("- expiresAfter: 9223372036854775000\n");
+    testYaml.append("  replayTimeoutSeconds: 922337203685477\n");
+
+    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+    ObjectReader reader = mapper.readerFor(Config.class);
+    Config config =
+        (Config) reader.readValues(new CharSequenceInputStream(testYaml, "utf-8")).next();
+
+    assertEquals(9223372036854775000L, config.getExpiresAfter());
+    assertEquals(922337203685477000L, config.getReplayTimeout());
   }
 }
diff --git a/src/test/java/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java b/src/test/java/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..78c2ae8206fbe5aac33ee4d9d9d73b2fae5965ef
--- /dev/null
+++ b/src/test/java/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java
@@ -0,0 +1,79 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.database.backend.transaction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import caosdb.server.datatype.ReferenceValue;
+import caosdb.server.entity.Entity;
+import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.wrapper.Property;
+import caosdb.server.entity.xml.PropertyToElementStrategyTest;
+import caosdb.server.query.Query.Selection;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class RetrieveFullEntityTest {
+
+  @Test
+  public void testRetrieveSubEntities() {
+    RetrieveFullEntity r =
+        new RetrieveFullEntity(0) {
+
+          /** Mock-up */
+          @Override
+          public void retrieveFullEntity(EntityInterface e, List<Selection> selections) {
+            // The id of the referenced window
+            assertEquals(1234, (int) e.getId());
+
+            // The level of selectors has been reduced by 1
+            assertEquals("description", selections.get(0).getSelector());
+
+            e.setDescription("A heart-shaped window.");
+          };
+        };
+
+    Property window = new Property(2345);
+    window.setName("Window");
+    window.setDatatype("Window");
+    window.setValue(new ReferenceValue(1234));
+
+    Entity house = new Entity();
+    house.addProperty(window);
+    ReferenceValue value = (ReferenceValue) house.getProperties().getEntityById(2345).getValue();
+    assertEquals(1234, (int) value.getId());
+    assertNull(value.getEntity());
+
+    List<Selection> selections = new ArrayList<>();
+    selections.add(PropertyToElementStrategyTest.parse("window.description"));
+
+    r.retrieveSubEntities(house, selections);
+
+    assertEquals(1234, (int) value.getId());
+    assertNotNull(value.getEntity());
+    assertEquals("A heart-shaped window.", value.getEntity().getDescription());
+  }
+}
diff --git a/src/test/java/caosdb/server/entity/SelectionTest.java b/src/test/java/caosdb/server/entity/SelectionTest.java
index cc961b9a5cfa47c022cfdf63201ea53613758a32..068a87c48a0257925d120613f83b4741173873d1 100644
--- a/src/test/java/caosdb/server/entity/SelectionTest.java
+++ b/src/test/java/caosdb/server/entity/SelectionTest.java
@@ -4,6 +4,8 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -22,13 +24,26 @@
  */
 package caosdb.server.entity;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import caosdb.server.CaosDBServer;
 import caosdb.server.entity.xml.SetFieldStrategy;
+import caosdb.server.query.Query;
 import caosdb.server.query.Query.Selection;
+import java.io.IOException;
 import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class SelectionTest {
 
+  @BeforeClass
+  public static void initServerProperties() throws IOException {
+    CaosDBServer.initServerProperties();
+  }
+
   @Test
   public void testEmpty1() {
     final SetFieldStrategy setFieldStrategy = new SetFieldStrategy();
@@ -59,7 +74,7 @@ public class SelectionTest {
     final Selection selection = new Selection("id");
     final SetFieldStrategy setFieldStrategy = (new SetFieldStrategy()).addSelection(selection);
 
-    Assert.assertFalse(setFieldStrategy.isToBeSet("name"));
+    Assert.assertTrue(setFieldStrategy.isToBeSet("name"));
   }
 
   @Test
@@ -262,16 +277,50 @@ public class SelectionTest {
             .addSelection(new Selection("blabla").setSubSelection(new Selection("value")))
             .addSelection(new Selection("blabla").setSubSelection(new Selection("description")));
 
-    Assert.assertTrue(setFieldStrategy.isToBeSet("blabla"));
-    Assert.assertTrue(setFieldStrategy.isToBeSet("id"));
-    Assert.assertTrue(setFieldStrategy.isToBeSet("name"));
-    Assert.assertFalse(setFieldStrategy.isToBeSet("bleb"));
+    assertTrue(setFieldStrategy.isToBeSet("blabla"));
+    assertTrue(setFieldStrategy.isToBeSet("id"));
+    assertTrue(setFieldStrategy.isToBeSet("name"));
+    assertFalse(setFieldStrategy.isToBeSet("bleb"));
 
     final SetFieldStrategy forProperty = setFieldStrategy.forProperty("blabla");
-    Assert.assertTrue(forProperty.isToBeSet("id"));
-    Assert.assertTrue(forProperty.isToBeSet("name"));
-    Assert.assertTrue(forProperty.isToBeSet("description"));
-    Assert.assertTrue(forProperty.isToBeSet("value"));
-    Assert.assertFalse(forProperty.isToBeSet("blub"));
+    assertTrue(forProperty.isToBeSet("id"));
+    assertTrue(forProperty.isToBeSet("name"));
+    assertTrue(forProperty.isToBeSet("description"));
+    assertTrue(forProperty.isToBeSet("value"));
+    assertFalse(forProperty.isToBeSet("blub"));
+  }
+
+  @Test
+  public void testSubSubSelection() {
+    String query_str = "SELECT property.subproperty.subsubproperty FROM ENTITY";
+    Query query = new Query(query_str);
+    query.parse();
+    assertEquals(query.getSelections().size(), 1);
+
+    Selection s = query.getSelections().get(0);
+    assertEquals(s.toString(), "property.subproperty.subsubproperty");
+    assertEquals(s.getSubselection().toString(), "subproperty.subsubproperty");
+    assertEquals(s.getSubselection().getSubselection().toString(), "subsubproperty");
+  }
+
+  @Test
+  public void testSubSubProperty() {
+    Selection s =
+        new Selection("property")
+            .setSubSelection(
+                new Selection("subproperty").setSubSelection(new Selection("subsubproperty")));
+
+    assertEquals(s.toString(), "property.subproperty.subsubproperty");
+
+    SetFieldStrategy setFieldStrategy = new SetFieldStrategy().addSelection(s);
+    assertTrue(setFieldStrategy.isToBeSet("property"));
+    assertFalse(setFieldStrategy.forProperty("property").isToBeSet("sadf"));
+    //    assertFalse(setFieldStrategy.forProperty("property").isToBeSet("name"));
+    assertTrue(setFieldStrategy.forProperty("property").isToBeSet("subproperty"));
+    assertTrue(
+        setFieldStrategy
+            .forProperty("property")
+            .forProperty("subproperty")
+            .isToBeSet("subsubproperty"));
   }
 }
diff --git a/src/test/java/caosdb/server/entity/container/PropertyContainerTest.java b/src/test/java/caosdb/server/entity/container/PropertyContainerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0678e83072a4f6f3f93aacb8a9f4196d5a566c2e
--- /dev/null
+++ b/src/test/java/caosdb/server/entity/container/PropertyContainerTest.java
@@ -0,0 +1,80 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.entity.container;
+
+import static org.junit.Assert.assertEquals;
+
+import caosdb.server.datatype.GenericValue;
+import caosdb.server.datatype.ReferenceValue;
+import caosdb.server.entity.Entity;
+import caosdb.server.entity.Role;
+import caosdb.server.entity.wrapper.Property;
+import caosdb.server.entity.xml.PropertyToElementStrategyTest;
+import caosdb.server.entity.xml.SetFieldStrategy;
+import org.jdom2.Element;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class PropertyContainerTest {
+
+  public static Entity house = null;
+  public static Property houseHeight = null;
+  public static Entity window = null;
+  public static Property windowHeight = null;
+  public static Entity houseOwner = null;
+  public static Property windowProperty = null;
+
+  @BeforeClass
+  public static void setup() {
+    window = new Entity(1234);
+    windowHeight = new Property(new Entity("window.height", Role.Property));
+    window.addProperty(windowHeight);
+    windowHeight.setValue(new GenericValue("windowHeight"));
+
+    houseOwner = new Entity("The Queen", Role.Record);
+
+    house = new Entity("Buckingham Palace", Role.Record);
+    houseHeight = new Property(new Entity("height", Role.Property));
+    houseHeight.setValue(new GenericValue("houseHeight"));
+    house.addProperty(houseHeight);
+    windowProperty = new Property(2345);
+    windowProperty.setName("window");
+    windowProperty.setValue(new ReferenceValue(window.getId()));
+    house.addProperty(windowProperty);
+
+    house.addProperty(new Property());
+    house.addProperty(new Property(houseHeight));
+  }
+
+  @Test
+  public void test() {
+    PropertyContainer container = new PropertyContainer(new Entity());
+    Element element = new Element("Record");
+    SetFieldStrategy setFieldStrategy =
+        new SetFieldStrategy().addSelection(PropertyToElementStrategyTest.parse("window.height"));
+
+    container.addToElement(windowProperty, element, setFieldStrategy);
+
+    assertEquals(1, element.getChildren().size());
+  }
+}
diff --git a/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java b/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d60ebd70feebe2e8e8810f296908e8b3a74100e
--- /dev/null
+++ b/src/test/java/caosdb/server/entity/xml/PropertyToElementStrategyTest.java
@@ -0,0 +1,105 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+package caosdb.server.entity.xml;
+
+import static org.junit.Assert.assertEquals;
+
+import caosdb.server.datatype.GenericValue;
+import caosdb.server.datatype.ReferenceValue;
+import caosdb.server.entity.Entity;
+import caosdb.server.entity.EntityInterface;
+import caosdb.server.entity.Role;
+import caosdb.server.entity.wrapper.Property;
+import caosdb.server.query.Query.Selection;
+import org.jdom2.Element;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PropertyToElementStrategyTest {
+
+  public static Entity house = null;
+  public static Property houseHeight = null;
+  public static Entity window = null;
+  public static Property windowHeight = null;
+  public static Entity houseOwner = null;
+  public static Property windowProperty = null;
+
+  /**
+   * Create a nested selection out of the dot-separated parts of <code>select</code>.
+   *
+   * <p>The returned Selection has nested subselections, so that each subselection corresponds to
+   * the next part and the remainder of the initial <code>select</code> String.
+   */
+  public static Selection parse(String select) {
+    String[] split = select.split("\\.");
+    Selection result = new Selection(split[0]);
+    Selection next = result;
+
+    for (int i = 1; i < split.length; i++) {
+      next.setSubSelection(new Selection(split[i]));
+      next = next.getSubselection();
+    }
+    return result;
+  }
+
+  @Before
+  public void setup() {
+    window = new Entity(1234, Role.Record);
+    windowHeight = new Property(new Entity("height", Role.Property));
+    window.addProperty(windowHeight);
+    windowHeight.setValue(new GenericValue("windowHeight"));
+
+    houseOwner = new Entity("The Queen", Role.Record);
+
+    house = new Entity("Buckingham Palace", Role.Record);
+    houseHeight = new Property(new Entity("height", Role.Property));
+    houseHeight.setValue(new GenericValue("houseHeight"));
+    house.addProperty(houseHeight);
+    windowProperty = new Property(2345);
+    windowProperty.setName("window");
+    windowProperty.setValue(new ReferenceValue(window.getId()));
+    house.addProperty(windowProperty);
+
+    house.addProperty(new Property());
+    house.addProperty(new Property(houseHeight));
+  }
+
+  @Test
+  public void test() {
+    PropertyToElementStrategy strategy = new PropertyToElementStrategy();
+    SetFieldStrategy setFieldStrategy = new SetFieldStrategy().addSelection(parse("height"));
+    EntityInterface property = windowProperty;
+    ((ReferenceValue) property.getValue()).setEntity(window, true);
+    Element element = strategy.toElement(property, setFieldStrategy);
+    assertEquals("Property", element.getName());
+    assertEquals("2345", element.getAttributeValue("id"));
+    assertEquals("window", element.getAttributeValue("name"));
+    assertEquals(1, element.getChildren().size());
+    assertEquals("Record", element.getChildren().get(0).getName());
+
+    Element recordElement = element.getChild("Record");
+    assertEquals("1234", recordElement.getAttributeValue("id"));
+    assertEquals(1, recordElement.getChildren().size());
+    assertEquals("windowHeight", recordElement.getChild("Property").getText());
+  }
+}
diff --git a/src/test/java/caosdb/server/permissions/EntityACLTest.java b/src/test/java/caosdb/server/permissions/EntityACLTest.java
index 13ebb6279546606c8e796672db09a7e99ffea588..6aae2257e5623d072c5f60e5991a5cb0248ecf74 100644
--- a/src/test/java/caosdb/server/permissions/EntityACLTest.java
+++ b/src/test/java/caosdb/server/permissions/EntityACLTest.java
@@ -23,14 +23,29 @@
 package caosdb.server.permissions;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import caosdb.server.CaosDBServer;
+import caosdb.server.accessControl.AnonymousAuthenticationToken;
+import caosdb.server.accessControl.AuthenticationUtils;
+import caosdb.server.accessControl.Config;
+import caosdb.server.accessControl.OneTimeAuthenticationToken;
+import caosdb.server.accessControl.Role;
+import caosdb.server.database.BackendTransaction;
+import caosdb.server.database.access.Access;
+import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl;
+import caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
+import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.resource.AbstractCaosDBServerResource;
 import caosdb.server.resource.AbstractCaosDBServerResource.XMLParser;
 import caosdb.server.utils.Utils;
 import java.io.IOException;
 import java.util.BitSet;
+import java.util.HashSet;
 import java.util.LinkedList;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
 import org.jdom2.Element;
 import org.jdom2.JDOMException;
 import org.junit.Assert;
@@ -47,10 +62,53 @@ public class EntityACLTest {
     return value;
   }
 
+  /** a no-op mock-up which resolved all rules to an empty set of permissions. */
+  public static class RetrievePermissionRulesMockup implements RetrievePermissionRulesImpl {
+
+    public RetrievePermissionRulesMockup(Access a) {}
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException {
+      return new HashSet<>();
+    }
+  }
+
+  /** a mock-up which returns null */
+  public static class RetrieveRoleMockup implements RetrieveRoleImpl {
+
+    public RetrieveRoleMockup(Access a) {}
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public Role retrieve(String role) throws TransactionException {
+      return null;
+    }
+  }
+
   @BeforeClass
   public static void init() throws IOException {
     CaosDBServer.initServerProperties();
+    CaosDBServer.initShiro();
     assertNotNull(EntityACL.GLOBAL_PERMISSIONS);
+
+    BackendTransaction.setImpl(
+        RetrievePermissionRulesImpl.class, RetrievePermissionRulesMockup.class);
+    BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRoleMockup.class);
   }
 
   @Test
@@ -172,35 +230,31 @@ public class EntityACLTest {
     Assert.assertEquals(convert(EntityACL.convert(EntityACL.OWNER_BITSET).get(1, 32)), 0);
   }
 
-  // @Test
-  // public void testDeserialize() {
-  // Assert.assertTrue(EntityACL.deserialize("{}") instanceof EntityACL);
-  // Assert.assertTrue(EntityACL.deserialize("{tf:134}") instanceof
-  // EntityACL);
-  // Assert.assertTrue(EntityACL.deserialize("{tf:6343;bla:884}") instanceof
-  // EntityACL);
-  // Assert.assertTrue(EntityACL.deserialize("{tf:-2835;bla:884}") instanceof
-  // EntityACL);
-  // Assert.assertTrue(EntityACL.deserialize("{?OWNER?:526;tahsdh   : -235;}")
-  // instanceof EntityACL);
-  // Assert.assertTrue(EntityACL.deserialize("{asdf:2345;}") instanceof
-  // EntityACL);
-  // Assert.assertTrue(raisesIllegalArguementException("{"));
-  // Assert.assertTrue(raisesIllegalArguementException("}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf:}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf:;}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{:234}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{:234;}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf:tf;}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf: +5259;}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf;}"));
-  // Assert.assertTrue(raisesIllegalArguementException("{tf:123223727356235782735235;}"));
-  // }
-
-  public boolean raisesIllegalArguementException(final String input) {
+  @Test
+  public void testDeserialize() {
+    Assert.assertTrue(EntityACL.deserialize("{}") instanceof EntityACL);
+    Assert.assertTrue(EntityACL.deserialize("{\"tf\":134}") instanceof EntityACL);
+    Assert.assertTrue(EntityACL.deserialize("{\"tf\":6343,\"bla\":884}") instanceof EntityACL);
+    Assert.assertTrue(EntityACL.deserialize("{\"tf\":-2835,\"bla\":884}") instanceof EntityACL);
+    Assert.assertTrue(
+        EntityACL.deserialize("{\"?OWNER?\":526,\"tahsdh   \": -235}") instanceof EntityACL);
+    Assert.assertTrue(EntityACL.deserialize("{\"asdf\":2345}") instanceof EntityACL);
+    Assert.assertTrue(raisesIllegalStateException("{"));
+    Assert.assertTrue(raisesIllegalStateException("}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf:}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf:;}"));
+    Assert.assertTrue(raisesIllegalStateException("{:234}"));
+    Assert.assertTrue(raisesIllegalStateException("{:234;}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf:tf;}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf: +5259;}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf;}"));
+    Assert.assertTrue(raisesIllegalStateException("{tf:123223727356235782735235;}"));
+  }
+
+  public boolean raisesIllegalStateException(final String input) {
     try {
       EntityACL.deserialize(input);
-    } catch (final IllegalArgumentException e) {
+    } catch (final IllegalStateException e) {
       return true;
     }
     return false;
@@ -212,134 +266,143 @@ public class EntityACLTest {
     return parser.parse(Utils.String2InputStream(s)).getRootElement();
   }
 
-  // @Test
-  // public void testParseFromElement() throws JDOMException, IOException {
-  // Assert.assertEquals("{}",
-  // EntityACL.serialize(EntityACL.parseFromElement(stringToJdom("<ACL></ACL>"))));
-  // Assert.assertEquals("{}", EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Grant></Grant></ACL>"))));
-  // Assert.assertEquals("{}", EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Deny></Deny></ACL>"))));
-  // Assert.assertEquals("{}", EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Grant role='bla'></Grant></ACL>"))));
-  // Assert.assertEquals("{}", EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Deny role='bla'></Deny></ACL>"))));
-  // Assert.assertEquals(
-  // "{bla:2;}",
-  // EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Grant role='bla'><Permission name='DELETE'
-  // /></Grant></ACL>"))));
-  // Assert.assertEquals(
-  // "{bla:" + (Long.MIN_VALUE + 2) + ";}",
-  // EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Deny role='bla'><Permission name='DELETE'
+  @Test
+  public void testEntityACLForAnonymous() {
+    Subject anonymous = SecurityUtils.getSubject();
+    anonymous.login(AnonymousAuthenticationToken.getInstance());
+    assertTrue(AuthenticationUtils.isAnonymous(anonymous));
+    EntityACL acl = EntityACL.getOwnerACLFor(anonymous);
+    assertNotNull(acl);
+    assertTrue(acl.getOwners().isEmpty());
+  }
+
+  //   @Test
+  //   public void testParseFromElement() throws JDOMException, IOException {
+  //   Assert.assertEquals("[]",
+  //   EntityACL.serialize(EntityACL.parseFromElement(stringToJdom("<ACL></ACL>"))));
+  //   Assert.assertEquals("[]", EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Grant></Grant></ACL>"))));
+  //   Assert.assertEquals("[]", EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Deny></Deny></ACL>"))));
+  //   Assert.assertEquals("[]", EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Grant role='bla'></Grant></ACL>"))));
+  //   Assert.assertEquals("[]", EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Deny role='bla'></Deny></ACL>"))));
+  //   Assert.assertEquals(
+  //   "{bla:2;}",
+  //   EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Grant role='bla'><Permission
+  // name='DELETE'/></Grant></ACL>"))));
+  //   Assert.assertEquals(
+  //   "{bla:" + (Long.MIN_VALUE + 2) + ";}",
+  //   EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Deny role='bla'><Permission name='DELETE'
   // /></Deny></ACL>"))));
-  // Assert.assertEquals(
-  // "{bla:32;}",
-  // EntityACL.serialize(EntityACL
-  // .parseFromElement(stringToJdom("<ACL><Grant role='bla'><Permission name='RETRIEVE:ACL'
+  //   Assert.assertEquals(
+  //   "{bla:32;}",
+  //   EntityACL.serialize(EntityACL
+  //   .parseFromElement(stringToJdom("<ACL><Grant role='bla'><Permission name='RETRIEVE:ACL'
   // /></Grant></ACL>"))));
-  // }
-
-  // @Test
-  // public void testFactory() {
-  // final EntityACLFactory f = new EntityACLFactory();
-  // f.grant("user1", "UPDATE:NAME");
-  // Assert.assertTrue((f.create().isPermitted("user1",
-  // EntityPermission.UPDATE_NAME)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.UPDATE_NAME)));
-  // f.grant("user2", "DELETE");
-  // Assert.assertFalse((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertTrue((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // f.deny("user2", 1);
-  // f.deny("user1", 1);
-  // Assert.assertFalse((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // f.grant("user1", true, 1);
-  // Assert.assertTrue((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // f.deny("user2", true, 1);
-  // Assert.assertTrue((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // f.grant("user2", true, 1);
-  // Assert.assertTrue((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // f.deny("user1", true, 1);
-  // Assert.assertFalse((f.create().isPermitted("user1",
-  // EntityPermission.DELETE)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.DELETE)));
-  // Assert.assertTrue((f.create().isPermitted("user1",
-  // EntityPermission.UPDATE_NAME)));
-  // Assert.assertFalse((f.create().isPermitted("user2",
-  // EntityPermission.UPDATE_NAME)));
-  // }
-
-  // @Test
-  // public void niceFactoryStuff() {
-  // final EntityACLFactory f = new EntityACLFactory();
-  // f.grant("user1", "*");
-  // final EntityACL acl1 = f.create();
-  // Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.EDIT_ACL));
-  // Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.DELETE));
-  // Assert.assertTrue(acl1.isPermitted("user1",
-  // EntityPermission.RETRIEVE_ENTITY));
-  // Assert.assertTrue(acl1.isPermitted("user1",
-  // EntityPermission.UPDATE_DATA_TYPE));
-  // Assert.assertTrue(acl1.isPermitted("user1",
-  // EntityPermission.USE_AS_PROPERTY));
+  //   }
+
+  @Test
+  public void testFactory() {
+    final EntityACLFactory f = new EntityACLFactory();
+
+    caosdb.server.permissions.Role role1 = caosdb.server.permissions.Role.create("role1");
+    Config config1 = new Config();
+    config1.setRoles(new String[] {role1.toString()});
+    OneTimeAuthenticationToken token1 = OneTimeAuthenticationToken.generate(config1);
+    Subject user1 = SecurityUtils.getSecurityManager().createSubject(null);
+    user1.login(token1);
+
+    caosdb.server.permissions.Role role2 = caosdb.server.permissions.Role.create("role2");
+    Config config2 = new Config();
+    config2.setRoles(new String[] {role2.toString()});
+    OneTimeAuthenticationToken token2 = OneTimeAuthenticationToken.generate(config2);
+    Subject user2 = SecurityUtils.getSecurityManager().createSubject(null);
+    user2.login(token2);
+
+    f.grant(role1, "UPDATE:NAME");
+    Assert.assertTrue((f.create().isPermitted(user1, EntityPermission.UPDATE_NAME)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.UPDATE_NAME)));
+    f.grant(role2, "DELETE");
+    Assert.assertFalse((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertTrue((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    f.deny(role2, 1);
+    f.deny(role1, 1);
+    Assert.assertFalse((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    f.grant(role1, true, 1);
+    Assert.assertTrue((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    f.deny(role2, true, 1);
+    Assert.assertTrue((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    f.grant(role2, true, 1);
+    Assert.assertTrue((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    f.deny(role1, true, 1);
+    Assert.assertFalse((f.create().isPermitted(user1, EntityPermission.DELETE)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.DELETE)));
+    Assert.assertTrue((f.create().isPermitted(user1, EntityPermission.UPDATE_NAME)));
+    Assert.assertFalse((f.create().isPermitted(user2, EntityPermission.UPDATE_NAME)));
+  }
+
+  //   @Test
+  //   public void niceFactoryStuff() {
+  //   final EntityACLFactory f = new EntityACLFactory();
+  //   f.grant("user1", "*");
+  //   final EntityACL acl1 = f.create();
+  //   Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.EDIT_ACL));
+  //   Assert.assertTrue(acl1.isPermitted("user1", EntityPermission.DELETE));
+  //   Assert.assertTrue(acl1.isPermitted("user1",
+  //   EntityPermission.RETRIEVE_ENTITY));
+  //   Assert.assertTrue(acl1.isPermitted("user1",
+  //   EntityPermission.UPDATE_DATA_TYPE));
+  //   Assert.assertTrue(acl1.isPermitted("user1",
+  //   EntityPermission.USE_AS_PROPERTY));
+  //
+  //   f.grant("?OWNER?", "DELETE", "EDIT:ACL", "RETRIEVE:*", "UPDATE:*",
+  //   "USE:*");
+  //   f.grant("user2", "EDIT:ACL");
+  //   final EntityACL acl2 = f.create();
+  //   Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.EDIT_ACL));
+  //   Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.DELETE));
+  //   Assert.assertTrue(acl2.isPermitted("user2",
+  //   EntityPermission.RETRIEVE_ENTITY));
+  //   Assert.assertTrue(acl2.isPermitted("user2",
+  //   EntityPermission.UPDATE_DATA_TYPE));
+  //   Assert.assertTrue(acl2.isPermitted("user2",
+  //   EntityPermission.USE_AS_PROPERTY));
   //
-  // f.grant("?OWNER?", "DELETE", "EDIT:ACL", "RETRIEVE:*", "UPDATE:*",
-  // "USE:*");
-  // f.grant("user2", "EDIT:ACL");
-  // final EntityACL acl2 = f.create();
-  // Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.EDIT_ACL));
-  // Assert.assertTrue(acl2.isPermitted("user2", EntityPermission.DELETE));
-  // Assert.assertTrue(acl2.isPermitted("user2",
-  // EntityPermission.RETRIEVE_ENTITY));
-  // Assert.assertTrue(acl2.isPermitted("user2",
-  // EntityPermission.UPDATE_DATA_TYPE));
-  // Assert.assertTrue(acl2.isPermitted("user2",
-  // EntityPermission.USE_AS_PROPERTY));
+  //   }
   //
-  // }
-
-  // @Test
-  // public void testDeny() {
-  // EntityACLFactory f = new EntityACLFactory();
-  // f.deny("test", "DELETE");
-  // Assert.assertFalse(f.create().isPermitted("test",
-  // EntityPermission.DELETE));
+  //   @Test
+  //   public void testDeny() {
+  //   EntityACLFactory f = new EntityACLFactory();
+  //   f.deny("test", "DELETE");
+  //   Assert.assertFalse(f.create().isPermitted("test",
+  //   EntityPermission.DELETE));
   //
-  // System.out.println(Utils.element2String(f.create().toElement()));
+  //   System.out.println(Utils.element2String(f.create().toElement()));
   //
-  // System.out.println(Utils.element2String(EntityACL.GLOBAL_PERMISSIONS.toElement()));
+  //   System.out.println(Utils.element2String(EntityACL.GLOBAL_PERMISSIONS.toElement()));
   //
-  // f.grant("test", "USE:*");
-  // Assert.assertFalse(f.create().isPermitted("test",
-  // EntityPermission.DELETE));
+  //   f.grant("test", "USE:*");
+  //   Assert.assertFalse(f.create().isPermitted("test",
+  //   EntityPermission.DELETE));
   //
-  // System.out.println(Utils.element2String(f.create().toElement()));
+  //   System.out.println(Utils.element2String(f.create().toElement()));
   //
-  // f = new EntityACLFactory();
-  // f.grant(EntityACL.OTHER_ROLE, "RETRIEVE:*");
-  // f.deny(EntityACL.OTHER_ROLE, "DELETE");
-  // final EntityACL a = f.create();
+  //   f = new EntityACLFactory();
+  //   f.grant(EntityACL.OTHER_ROLE, "RETRIEVE:*");
+  //   f.deny(EntityACL.OTHER_ROLE, "DELETE");
+  //   final EntityACL a = f.create();
   //
-  // System.out.println(Utils.element2String(a.toElement()));
+  //   System.out.println(Utils.element2String(a.toElement()));
   //
-  // System.out.println(Utils.element2String(EntityACL.deserialize(a.serialize()).toElement()));
-  // }
+  //   System.out.println(Utils.element2String(EntityACL.deserialize(a.serialize()).toElement()));
+  //   }
 
 }
diff --git a/src/test/java/caosdb/server/query/TestCQL.java b/src/test/java/caosdb/server/query/TestCQL.java
index 6006297dac0cd029ee451c0349f2b11e7ac2f86e..402cddd2be631291d772d494804cf21bc9dbc6b7 100644
--- a/src/test/java/caosdb/server/query/TestCQL.java
+++ b/src/test/java/caosdb/server/query/TestCQL.java
@@ -167,8 +167,11 @@ public class TestCQL {
   String ticket187 = "FIND Entity WITH pname=-1";
   String ticket207 = "FIND RECORD WHICH REFERENCES 10594";
   String query33 = "FIND ename WITH a date IN 2015";
+  String query33a = "FIND ename WITH a date IN \"2015\"";
   String query34 = "FIND ename WITH a date NOT IN 2015";
+  String query34a = "FIND ename WITH a date NOT IN \"2015\"";
   String query35 = "FIND ename WITH a date IN 2015-01-01";
+  String query35a = "FIND ename WITH a date IN \"2015-01-01\"";
   String query36 = "FIND ename.pname LIKE \"wil*card\"";
   String query37 = "FIND ename.pname LIKE wil*card";
   String query38 = "FIND ename WHICH HAS A pname LIKE \"wil*\"";
@@ -4671,6 +4674,45 @@ public class TestCQL {
     assertEquals("2015", pov.getValue());
   }
 
+  /** String query33a = "FIND ename WITH a date IN \"2015\""; */
+  @Test
+  public void testQuery33a() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query33a));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 4 children: FIND, role, WHICHCLAUSE, EOF
+    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("ename", sfq.getChild(1).getText());
+    assertEquals("WITHadateIN\"2015\"", sfq.getChild(2).getText());
+    assertEquals("ename", sfq.e.toString());
+    assertNull(sfq.r);
+    assertEquals("POV", sfq.filter.getClass().getSimpleName());
+
+    final ParseTree whichclause = sfq.getChild(2);
+    // 2 children; WHICH, transaction
+    assertEquals(2, whichclause.getChildCount());
+    assertEquals("WITHa", whichclause.getChild(0).getText());
+    assertEquals("dateIN\"2015\"", whichclause.getChild(1).getText());
+
+    final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
+    assertEquals(3, transactionFilter.getChildCount());
+    assertEquals("date", transactionFilter.getChild(0).getText());
+    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("\"2015\"", transactionFilter.getChild(2).getText());
+
+    assertTrue(sfq.filter instanceof POV);
+    final POV pov = (POV) sfq.filter;
+    assertEquals("(", pov.getOperator());
+    assertEquals("2015", pov.getValue());
+  }
+
   /** String query34 = "FIND ename WITH a date NOT IN 2015"; */
   @Test
   public void testQuery34() {
@@ -4711,6 +4753,46 @@ public class TestCQL {
     assertEquals("2015", pov.getValue());
   }
 
+  /** String query34a = "FIND ename WITH a date NOT IN \"2015\""; */
+  @Test
+  public void testQuery34a() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query34a));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 4 children: FIND, role, WHICHCLAUSE, EOF
+    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("ename", sfq.getChild(1).getText());
+    assertEquals("WITHadateNOTIN\"2015\"", sfq.getChild(2).getText());
+    assertEquals("ename", sfq.e.toString());
+    assertNull(sfq.r);
+    assertEquals("POV", sfq.filter.getClass().getSimpleName());
+
+    final ParseTree whichclause = sfq.getChild(2);
+    // 2 children: WHICH, POV
+    assertEquals(2, whichclause.getChildCount());
+    assertEquals("WITHa", whichclause.getChild(0).getText());
+    assertEquals("dateNOTIN\"2015\"", whichclause.getChild(1).getText());
+
+    final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
+    assertEquals(4, transactionFilter.getChildCount());
+    assertEquals("date", transactionFilter.getChild(0).getText());
+    assertEquals("NOT", transactionFilter.getChild(1).getText());
+    assertEquals("IN", transactionFilter.getChild(2).getText());
+    assertEquals("\"2015\"", transactionFilter.getChild(3).getText());
+
+    assertTrue(sfq.filter instanceof POV);
+    final POV pov = (POV) sfq.filter;
+    assertEquals("!(", pov.getOperator());
+    assertEquals("2015", pov.getValue());
+  }
+
   /** String query35 = "FIND ename WITH a date IN 2015-01-01"; */
   @Test
   public void testQuery35() {
@@ -4750,6 +4832,45 @@ public class TestCQL {
     assertEquals("2015-01-01", pov.getValue());
   }
 
+  /** String query35a = "FIND ename WITH a date IN \"2015-01-01\""; */
+  @Test
+  public void testQuery35a() {
+    CQLLexer lexer;
+    lexer = new CQLLexer(CharStreams.fromString(this.query35a));
+    final CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+    final CQLParser parser = new CQLParser(tokens);
+    final CqContext sfq = parser.cq();
+
+    System.out.println(sfq.toStringTree(parser));
+
+    // 4 children: FIND, role, WHICHCLAUSE, EOF
+    assertEquals(4, sfq.getChildCount());
+    assertEquals("FIND", sfq.getChild(0).getText());
+    assertEquals("ename", sfq.getChild(1).getText());
+    assertEquals("WITHadateIN\"2015-01-01\"", sfq.getChild(2).getText());
+    assertEquals("ename", sfq.e.toString());
+    assertNull(sfq.r);
+    assertEquals("POV", sfq.filter.getClass().getSimpleName());
+
+    final ParseTree whichclause = sfq.getChild(2);
+    // 2 children: WHICH, POV
+    assertEquals(2, whichclause.getChildCount());
+    assertEquals("WITHa", whichclause.getChild(0).getText());
+    assertEquals("dateIN\"2015-01-01\"", whichclause.getChild(1).getText());
+
+    final ParseTree transactionFilter = whichclause.getChild(1).getChild(0);
+    assertEquals(3, transactionFilter.getChildCount());
+    assertEquals("date", transactionFilter.getChild(0).getText());
+    assertEquals("IN", transactionFilter.getChild(1).getText());
+    assertEquals("\"2015-01-01\"", transactionFilter.getChild(2).getText());
+
+    assertTrue(sfq.filter instanceof POV);
+    final POV pov = (POV) sfq.filter;
+    assertEquals("(", pov.getOperator());
+    assertEquals("2015-01-01", pov.getValue());
+  }
+
   /** String query36 = "FIND ename.pname LIKE \"wil*card\""; */
   @Test
   public void testQuery36() {
diff --git a/src/test/java/caosdb/server/resource/TestAbstractCaosDBServerResource.java b/src/test/java/caosdb/server/resource/TestAbstractCaosDBServerResource.java
index 81baabd4797842f1cbbc61f990d6ea580553de85..59f0a1d5812b1127ea0ff4514520bc30039af5cd 100644
--- a/src/test/java/caosdb/server/resource/TestAbstractCaosDBServerResource.java
+++ b/src/test/java/caosdb/server/resource/TestAbstractCaosDBServerResource.java
@@ -8,12 +8,21 @@ import caosdb.server.CaosDBServer;
 import caosdb.server.ServerProperties;
 import caosdb.server.accessControl.AnonymousAuthenticationToken;
 import caosdb.server.accessControl.AnonymousRealm;
+import caosdb.server.accessControl.Role;
+import caosdb.server.database.BackendTransaction;
+import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.implementation.MySQL.ConnectionException;
+import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl;
+import caosdb.server.database.backend.interfaces.RetrieveRoleImpl;
+import caosdb.server.database.exceptions.TransactionException;
+import caosdb.server.database.misc.TransactionBenchmark;
+import caosdb.server.permissions.PermissionRule;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
 import java.sql.SQLException;
+import java.util.HashSet;
 import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.support.DelegatingSubject;
@@ -29,9 +38,51 @@ public class TestAbstractCaosDBServerResource {
 
   @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
 
+  /** a no-op mock-up which resolved all rules to an empty set of permissions. */
+  public static class RetrievePermissionRulesMockup implements RetrievePermissionRulesImpl {
+
+    public RetrievePermissionRulesMockup(Access a) {}
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException {
+      return new HashSet<>();
+    }
+  }
+
+  /** a no-op mock-up which returns null */
+  public static class RetrieveRoleMockup implements RetrieveRoleImpl {
+
+    public RetrieveRoleMockup(Access a) {}
+
+    @Override
+    public void setTransactionBenchmark(TransactionBenchmark b) {}
+
+    @Override
+    public TransactionBenchmark getBenchmark() {
+      return null;
+    }
+
+    @Override
+    public Role retrieve(String role) throws TransactionException {
+      return null;
+    }
+  }
+
   @BeforeClass
   public static void initServerProperties() throws IOException {
     CaosDBServer.initServerProperties();
+
+    BackendTransaction.setImpl(
+        RetrievePermissionRulesImpl.class, RetrievePermissionRulesMockup.class);
+    BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRoleMockup.class);
   }
 
   @Test
diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java
index 2edfb82b53b56324996293a469fadf807264d86c..3280ae3af5a62174006b504408d7c3bde8a2632e 100644
--- a/src/test/java/caosdb/server/resource/TestScriptingResource.java
+++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java
@@ -23,14 +23,13 @@
 package caosdb.server.resource;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import caosdb.server.CaosDBServer;
-import caosdb.server.accessControl.AuthenticationUtils;
+import caosdb.server.accessControl.AnonymousAuthenticationToken;
 import caosdb.server.accessControl.CredentialsValidator;
 import caosdb.server.accessControl.Principal;
 import caosdb.server.accessControl.Role;
-import caosdb.server.accessControl.SessionToken;
+import caosdb.server.accessControl.UserSources;
 import caosdb.server.database.BackendTransaction;
 import caosdb.server.database.access.Access;
 import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl;
@@ -42,16 +41,16 @@ import caosdb.server.database.misc.TransactionBenchmark;
 import caosdb.server.database.proto.ProtoUser;
 import caosdb.server.entity.Message;
 import caosdb.server.permissions.PermissionRule;
+import caosdb.server.scripting.ScriptingPermissions;
+import caosdb.server.scripting.ServerSideScriptingCaller;
 import java.io.IOException;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.config.Ini;
-import org.apache.shiro.config.IniSecurityManagerFactory;
-import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.authz.permission.WildcardPermission;
 import org.apache.shiro.subject.Subject;
-import org.apache.shiro.util.Factory;
+import org.jdom2.Element;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.restlet.Request;
@@ -93,7 +92,13 @@ public class TestScriptingResource {
 
     @Override
     public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException {
-      return new HashSet<>();
+      HashSet<PermissionRule> result = new HashSet<>();
+      result.add(
+          new PermissionRule(
+              true,
+              false,
+              new WildcardPermission(ScriptingPermissions.PERMISSION_EXECUTION("anonymous_ok"))));
+      return result;
     }
 
     @Override
@@ -149,19 +154,16 @@ public class TestScriptingResource {
 
   @BeforeClass
   public static void setupShiro() throws IOException {
+    CaosDBServer.initServerProperties();
+    CaosDBServer.initShiro();
+
     BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRole.class);
     BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class);
     BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUser.class);
     BackendTransaction.setImpl(
         RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class);
 
-    CaosDBServer.initServerProperties();
-    Ini config = CaosDBServer.getShiroConfig();
-    final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config);
-
-    final SecurityManager securityManager = factory.getInstance();
-
-    SecurityUtils.setSecurityManager(securityManager);
+    UserSources.getDefaultRealm();
   }
 
   ScriptingResource resource =
@@ -173,23 +175,25 @@ public class TestScriptingResource {
             java.util.List<caosdb.server.entity.FileProperties> files,
             Object authToken)
             throws Message {
+          if (invokation.get(0).equals("anonymous_ok")) {
+            return 0;
+          }
           return -1;
         };
 
         @Override
-        public Object generateAuthToken() {
+        public Element generateRootElement(ServerSideScriptingCaller caller) {
+          return new Element("OK");
+        };
+
+        @Override
+        public Object generateAuthToken(String purpose) {
           return "";
         }
       };
 
   @Test
   public void testUnsupportedMediaType() {
-    Subject user = SecurityUtils.getSubject();
-    if (user.isAuthenticated()) {
-      user.logout();
-    }
-    SessionToken t = SessionToken.generate(new Principal("CaosDB", "user"), null);
-    user.login(t);
     Representation entity = new StringRepresentation("asdf");
     entity.setMediaType(MediaType.TEXT_ALL);
     Request request = new Request(Method.POST, "../test", entity);
@@ -201,12 +205,11 @@ public class TestScriptingResource {
   }
 
   @Test
-  public void testAnonymous() {
+  public void testAnonymousWithOutPermission() {
     Subject user = SecurityUtils.getSubject();
-    user.login(AuthenticationUtils.ANONYMOUS_USER);
-    assertTrue(resource.isAnonymous());
-    Representation entity = new StringRepresentation("asdf");
-    entity.setMediaType(MediaType.TEXT_ALL);
+    user.login(AnonymousAuthenticationToken.getInstance());
+    Form form = new Form("call=anonymous_no_permission");
+    Representation entity = form.getWebRepresentation();
     Request request = new Request(Method.POST, "../test", entity);
     request.setRootRef(new Reference("bla"));
     request.getAttributes().put("SRID", "asdf1234");
@@ -218,6 +221,23 @@ public class TestScriptingResource {
     assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getResponse().getStatus());
   }
 
+  @Test
+  public void testAnonymousWithPermission() {
+    Subject user = SecurityUtils.getSubject();
+    user.login(AnonymousAuthenticationToken.getInstance());
+    Form form = new Form("call=anonymous_ok");
+    Representation entity = form.getWebRepresentation();
+    Request request = new Request(Method.POST, "../test", entity);
+    request.setRootRef(new Reference("bla"));
+    request.getAttributes().put("SRID", "asdf1234");
+    request.setDate(new Date());
+    request.setHostRef("bla");
+    resource.init(null, request, new Response(null));
+
+    resource.handle();
+    assertEquals(Status.SUCCESS_OK, resource.getResponse().getStatus());
+  }
+
   @Test
   public void testForm2invocation() throws Message {
     Form form =
@@ -235,8 +255,9 @@ public class TestScriptingResource {
 
   @Test
   public void testHandleForm() throws Message, IOException {
-    Form form =
-        new Form("-Ooption=OPTION&call=CALL&-Ooption2=OPTION2&-p=POS1&-p4=POS3&-p2=POS2&IGNORED");
-    assertEquals(-1, resource.handleForm(form));
+    Subject user = SecurityUtils.getSubject();
+    user.login(AnonymousAuthenticationToken.getInstance());
+    Form form = new Form("call=anonymous_ok");
+    assertEquals(0, resource.handleForm(form));
   }
 }
diff --git a/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java
index 4ff5d7185d43704e4bc97ef435a5c853e58e3eb8..075c8d998acdfc7f897f9429de468034a4dc022e 100644
--- a/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java
+++ b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java
@@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import caosdb.server.CaosDBServer;
+import caosdb.server.ServerProperties;
 import java.io.IOException;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -26,7 +27,13 @@ public class WebinterfaceUtilsTest {
     WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path"));
     String buildNumber = utils.getBuildNumber();
     String ref = utils.getWebinterfaceURI("sub");
-    assertEquals("https://host:2345/webinterface/" + buildNumber + "/sub", ref);
+    String contextRoot = CaosDBServer.getServerProperty(ServerProperties.KEY_CONTEXT_ROOT);
+    contextRoot =
+        contextRoot != null && contextRoot.length() > 0
+            ? "/" + contextRoot.replaceFirst("^/", "").replaceFirst("/$", "")
+            : "";
+
+    assertEquals("https://host:2345" + contextRoot + "/webinterface/" + buildNumber + "/sub", ref);
   }
 
   @Test