diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73403111ac141553237a3f1691adc22ad2183a16..aa52d2b98fef19a8b1daffde3fb76e9107b6fa13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added ###
 
+* two new `password_method`s for the `pycaosdb.ini` and the
+  `configure_connection` function: `unauthenticated` for staying
+  unauthenticated (and using the anonymous user) and `auth_token`. If
+  `password_method == "auth_token"` the `auth_token` option is required as
+  well.
 * Empty string support (See caosdb-server#33)
 
 ### Changed ###
@@ -22,6 +27,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Deprecated ###
 
+* Setting the `auth_token` option without setting the `password_method` to
+  `auth_token`. This affects both the `pycaosdb.ini` and the
+  `configure_connection` function. During the deprecation phase it will be
+  assumed that `password_method` is `auth_token` if the the `auth_token` is
+  set.
+
 ### Removed ###
 
 ### Fixed ###
diff --git a/setup.py b/setup.py
index f3c2c11b6d2498692dc3dd4be4d356711a1f991d..33300809a3fda5df1eb3153290c90ac715bc0dab 100755
--- a/setup.py
+++ b/setup.py
@@ -164,7 +164,8 @@ def setup_package():
         tests_require=["pytest", "pytest-cov", "coverage>=4.4.2"],
         package_data={
             'caosdb': ['cert/indiscale.ca.crt'],
-        }
+        },
+        scripts=["src/caosdb/utils/caosdb_admin.py"]
     )
     try:
         setup(**metadata)
diff --git a/src/caosdb/__init__.py b/src/caosdb/__init__.py
index 92922f79c6b5105abc5e849175c4271561dd92a9..75847bb15b8a64345119e0f0aefc00bd9f116081 100644
--- a/src/caosdb/__init__.py
+++ b/src/caosdb/__init__.py
@@ -41,6 +41,7 @@ from caosdb.common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED,
 from caosdb.configuration import configure, get_config
 from caosdb.connection.connection import configure_connection, get_connection
 from caosdb.exceptions import *
+from caosdb.version import version as __version__
 
 # read configuration these files
 
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index adbd9dc3a9039999f0124f0a6fe046ee2a9dd44f..0134deef6ac071f044e261947a9dfaa0594d1d33 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -538,13 +538,17 @@ class Entity(object):
                 if p.id is not None and int(p.id) == int(key):
                     return p
         elif isinstance(key, Entity):
-            if self.id is not None:
-                return self.get_parent(int(key.id))
-            else:
-                return self.get_parent(key.name)
+            if key.id is not None:
+                # first try by id
+                found = self.get_parent(int(key.id))
+                if found is not None:
+                    return found
+            # otherwise by name
+            return self.get_parent(key.name)
         else:
             for p in self.parents:
-                if p.name is not None and str(p.name) == str(key):
+                if (p.name is not None
+                        and str(p.name).lower() == str(key).lower()):
                     return p
 
         return None
@@ -3522,6 +3526,14 @@ class DropOffBox(list):
         return self
 
 
+class UserInfo():
+
+    def __init__(self, xml):
+        self.roles = [role.text for role in xml.findall("Roles/Role")]
+        self.name = xml.get("username")
+        self.realm = xml.get("realm")
+
+
 class Info():
 
     def __init__(self):
@@ -3540,7 +3552,10 @@ class Info():
 
         for e in xml:
             m = _parse_single_xml_element(e)
-            self.messages.append(m)
+            if isinstance(m, UserInfo):
+                self.user_info = m
+            else:
+                self.messages.append(m)
 
     def __str__(self):
         if "counts" not in self.messages:
@@ -3663,6 +3678,8 @@ def _parse_single_xml_element(elem):
         return ACL(xml=elem)
     elif elem.tag == "Permissions":
         return Permissions(xml=elem)
+    elif elem.tag == "UserInfo":
+        return UserInfo(xml=elem)
     else:
         return Message(type=elem.tag, code=elem.get(
             "code"), description=elem.get("description"), body=elem.text)
diff --git a/src/caosdb/connection/authentication/auth_token.py b/src/caosdb/connection/authentication/auth_token.py
new file mode 100644
index 0000000000000000000000000000000000000000..fbce78fb86d78e06d97ef00dd162c1ed57f7560d
--- /dev/null
+++ b/src/caosdb/connection/authentication/auth_token.py
@@ -0,0 +1,96 @@
+#! -*- 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 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <f.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
+#
+"""auth_token.
+
+An Authentictor which only uses only a pre-supplied authentication token.
+"""
+from __future__ import absolute_import, unicode_literals, print_function
+from .interface import AbstractAuthenticator, CaosDBServerConnection
+from caosdb.connection.utils import auth_token_to_cookie
+from caosdb.exceptions import LoginFailedException
+
+
+def get_authentication_provider():
+    """get_authentication_provider.
+
+    Return an authenticator which only uses a pre-supplied authentication
+    token.
+
+    Returns
+    -------
+    AuthTokenAuthenticator
+    """
+    return AuthTokenAuthenticator()
+
+
+class AuthTokenAuthenticator(AbstractAuthenticator):
+    """AuthTokenAuthenticator.
+
+    Subclass of AbstractAuthenticator which provides authentication only via
+    a given authentication token.
+
+    Methods
+    -------
+    login
+    logout
+    configure
+    """
+
+    def __init__(self):
+        super(AuthTokenAuthenticator, self).__init__()
+        self.auth_token = None
+        self._connection = None
+
+    def login(self):
+        self._login()
+
+    def _login(self):
+        raise LoginFailedException("The authentication token is expired or you "
+                                   "have been logged out otherwise. The "
+                                   "auth_token authenticator cannot log in "
+                                   "again. You must provide a new "
+                                   "authentication token.")
+
+    def logout(self):
+        self._logout()
+
+    def _logout(self):
+        self.logger.debug("[LOGOUT]")
+        if self.auth_token is not None:
+            headers = {'Cookie': auth_token_to_cookie(self.auth_token)}
+            self._connection.request(method="DELETE", path="logout",
+                                     headers=headers)
+        self.auth_token = None
+
+    def configure(self, **config):
+        if "auth_token" in config:
+            self.auth_token = config["auth_token"]
+        if "connection" in config:
+            self._connection = config["connection"]
+            if not isinstance(self._connection, CaosDBServerConnection):
+                raise Exception("""Bad configuration of the caosdb connection.
+                                The `connection` must be an instance of
+                                `CaosDBConnection`.""")
diff --git a/src/caosdb/connection/authentication/interface.py b/src/caosdb/connection/authentication/interface.py
index f156345afcb9d77c118daa7bf53b57b8499d87cc..d9a9b4306b1dbdedbc67dfda81a3ca3d7b4aeb41 100644
--- a/src/caosdb/connection/authentication/interface.py
+++ b/src/caosdb/connection/authentication/interface.py
@@ -50,6 +50,8 @@ class AbstractAuthenticator(ABC):
     logger : Logger
         A logger which should be used for all logging which has to do with
         authentication.
+    auth_token : str
+        A string representation of a CaosDB Auth Token.
 
     Methods
     -------
@@ -59,10 +61,6 @@ class AbstractAuthenticator(ABC):
     on_request
     on_response
 
-    Attributes
-    ----------
-    auth_token : str
-        A string representation of a CaosDB Auth Token.
     """
 
     def __init__(self):
@@ -187,7 +185,7 @@ class CredentialsAuthenticator(AbstractAuthenticator):
         self._logout()
 
     def _logout(self):
-        _LOGGER.debug("[LOGOUT]")
+        self.logger.debug("[LOGOUT]")
         if self.auth_token is not None:
             self._connection.request(method="DELETE", path="logout")
         self.auth_token = None
@@ -195,7 +193,7 @@ class CredentialsAuthenticator(AbstractAuthenticator):
     def _login(self):
         username = self._credentials_provider.username
         password = self._credentials_provider.password
-        _LOGGER.debug("[LOGIN] %s", username)
+        self.logger.debug("[LOGIN] %s", username)
 
         # we need a username for this:
         if username is None:
diff --git a/src/caosdb/connection/authentication/unauthenticated.py b/src/caosdb/connection/authentication/unauthenticated.py
new file mode 100644
index 0000000000000000000000000000000000000000..53a2756eb59259a0be012e41f2ea213735568838
--- /dev/null
+++ b/src/caosdb/connection/authentication/unauthenticated.py
@@ -0,0 +1,119 @@
+#! -*- 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 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <f.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
+#
+"""unauthenticated.
+
+An Authenticator which suppresses any authentication and also ignores auth_token
+cookies.
+"""
+from __future__ import absolute_import, unicode_literals, print_function
+from .interface import AbstractAuthenticator, CaosDBServerConnection
+from caosdb.exceptions import LoginFailedException
+
+
+def get_authentication_provider():
+    """get_authentication_provider.
+
+    Return an authenticator which only uses a pre-supplied authentication
+    token.
+
+    Returns
+    -------
+    AuthTokenAuthenticator
+    """
+    return Unauthenticated()
+
+
+class Unauthenticated(AbstractAuthenticator):
+    """Unauthenticated.
+
+    Subclass of AbstractAuthenticator which suppresses any authentication and
+    also ignores auth_token cookies.
+
+    Methods
+    -------
+    login
+    logout
+    configure
+    on_request
+    on_response
+    """
+
+    def __init__(self):
+        super(Unauthenticated, self).__init__()
+        self.auth_token = None
+        self._connection = None
+
+    def login(self):
+        self._login()
+
+    def _login(self):
+        raise LoginFailedException("This caosdb client is configured to stay "
+                                   "unauthenticated. Change your "
+                                   "`password_method` and provide an "
+                                   "`auth_token` or credentials if you want "
+                                   "to authenticate this client.")
+
+    def logout(self):
+        self._logout()
+
+    def _logout(self):
+        self.auth_token = None
+
+    def configure(self, **config):
+        self.auth_token = None
+
+    def on_request(self, method, path, headers, **kwargs):
+        # pylint: disable=unused-argument
+        """on_request.
+
+        This implementation does not attempt to login or authenticate in any
+        form.
+
+        Parameters
+        ----------
+        method
+            unused
+        path
+            unused
+        headers
+            unused
+        **kwargs
+            unused
+        """
+        pass
+
+    def on_response(self, response):
+        # pylint: disable=unused-argument
+        """on_response.
+
+        This implementation ignores any auth_token cookie sent by the server.
+
+        Parameters
+        ----------
+        response
+            unused
+        """
+        pass
diff --git a/src/caosdb/connection/connection.py b/src/caosdb/connection/connection.py
index 5fbca32ed9a8d8f5028b340821c84a2b65ef03a4..65c8413c251ad42f44b77e37ac34a26b1c329c0d 100644
--- a/src/caosdb/connection/connection.py
+++ b/src/caosdb/connection/connection.py
@@ -262,8 +262,8 @@ def _get_authenticator(**config):
     ----------
     password_method : str
         The simple name of a submodule of caosdb.connection.authentication.
-        Currently, there are three valid values for this parameter: 'plain',
-        'pass', and 'keyring'.
+        Currently, there are four valid values for this parameter: 'plain',
+        'pass', 'keyring' and 'auth_token'.
     **config :
         Any other keyword arguments are passed the configre method of the
         password_method.
@@ -293,7 +293,8 @@ def _get_authenticator(**config):
 
     except ImportError:
         raise ConfigurationException("Password method \"{}\" not implemented. "
-                                     "Valid methods: plain, pass, or keyring."
+                                     "Try `plain`, `pass`, `keyring`, or "
+                                     "`auth_token`."
                                      .format(config["password_method"]))
 
 
@@ -325,6 +326,7 @@ def configure_connection(**kwargs):
         - "input"    Asks for the password.
         - "pass"     Uses the `pass` password manager.
         - "keyring"  Uses the `keyring` library.
+        - "auth_token" Uses only a given auth_token.
 
     timeout : int
         A connection timeout in seconds. (Default: 210)
@@ -333,6 +335,10 @@ def configure_connection(**kwargs):
         Whether SSL certificate warnings should be ignored. Only use this for
         development purposes! (Default: False)
 
+    auth_token : str (optional)
+        An authentication token which has been issued by the CaosDB Server.
+        Implies `password_method="auth_token"` if set.  An example token string would be `["O","OneTimeAuthenticationToken","anonymous",["administration"],[],1592995200000,604800000,"3ZZ4WKRB-5I7DG2Q6-ZZE6T64P-VQ","197d0d081615c52dc18fb323c300d7be077beaad4020773bb58920b55023fa6ee49355e35754a4277b9ac525c882bcd3a22e7227ba36dfcbbdbf8f15f19d1ee9",1,30000]`.
+
     implementation : CaosDBServerConnection
         The class which implements the connection. (Default:
         _DefaultCaosDBServerConnection)
@@ -466,6 +472,9 @@ class _Connection(object):  # pylint: disable=useless-object-inheritance
                 "or a factory).", type_err)
         self._delegate_connection.configure(**config)
 
+        if "auth_token" in config:
+            # deprecated, needed for older scripts
+            config["password_method"] = "auth_token"
         if "password_method" not in config:
             raise ConfigurationException("Missing password_method. You did "
                                          "not specify a `password_method` for"
@@ -473,9 +482,6 @@ class _Connection(object):  # pylint: disable=useless-object-inheritance
         self._authenticator = _get_authenticator(
             connection=self._delegate_connection, **config)
 
-        if "auth_token" in config:
-            self._authenticator.auth_token = config["auth_token"]
-
         return self
 
     def retrieve(self, entity_uri_segments=None, query_dict=None, **kwargs):
@@ -598,7 +604,7 @@ class _Connection(object):  # pylint: disable=useless-object-inheritance
             body=body, **kwargs)
         _LOGGER.debug("response: %s %s", str(http_response.status),
                       str(http_response.getheaders()))
-        _handle_response_status(http_response)
         self._authenticator.on_response(http_response)
+        _handle_response_status(http_response)
 
         return http_response
diff --git a/src/caosdb/utils/caosdb_admin.py b/src/caosdb/utils/caosdb_admin.py
index 46ca9b1baaf66e3380af342d3c0e8e3a489b21e7..250c2878d5d615b0815bdd7b0bb287d1567fe085 100755
--- a/src/caosdb/utils/caosdb_admin.py
+++ b/src/caosdb/utils/caosdb_admin.py
@@ -284,6 +284,15 @@ USAGE
                             formatter_class=RawDescriptionHelpFormatter)
     parser.add_argument('-V', '--version', action='version',
                         version=program_version_message)
+    parser.add_argument("--auth-token", metavar="AUTH_TOKEN",
+                        dest="auth_token",
+                        help=("A CaosDB authentication token (default: None). "
+                              "If the authentication token is passed, the "
+                              "`password_method` of the connection is set to "
+                              "`auth_token` and the respective configuration "
+                              "from the pycaosdb.ini is effectively being "
+                              "overridden.\nTODO: Also allow passing the token "
+                              "via environmenty variables."))
     subparsers = parser.add_subparsers(
         title="commands",
         metavar="COMMAND",
@@ -600,8 +609,12 @@ USAGE
 
     # Process arguments
     args = parser.parse_args()
-
-    db.configure_connection()._login()
+    auth_token = args.auth_token
+    if auth_token is not None:
+        db.configure_connection(password_method="auth_token",
+                                auth_token=auth_token)
+    else:
+        db.configure_connection()
 
     return args.call(args)
 
diff --git a/src/caosdb/utils/server_side_scripting.py b/src/caosdb/utils/server_side_scripting.py
new file mode 100644
index 0000000000000000000000000000000000000000..663178dcbda4293cb30dff88efbfb7b7302df70d
--- /dev/null
+++ b/src/caosdb/utils/server_side_scripting.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+#
+# ** 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
+#
+"""server_side_scripting
+
+Helper functions for calling server-side scripts.
+"""
+from urllib.parse import quote
+from lxml import etree
+
+from caosdb.connection.connection import get_connection
+from caosdb.connection.utils import urlencode
+from caosdb.connection.encode import MultipartParam, multipart_encode
+
+
+def _make_params(pos_args, opts):
+    """Create and return option string components.
+
+The return value is a dict with be something like `-O<key>`:`<value>` from `opts` and
+`-p{0,1,2,3,...}`:`<value>` from `pos_args`.
+
+    """
+    result = {}
+    for key, val in opts.items():
+        result["-O{key}".format(key=key)] = str(val)
+    for i, val in enumerate(pos_args):
+        result["-p{i}".format(i=i)] = str(val)
+    return result
+
+
+def _make_multipart_request(call, pos_args, opts, files):
+    """Return body and header for an HTTP request.
+    """
+    parts = list()
+    params = _make_params(pos_args, opts)
+
+    parts.append(MultipartParam("call", call))
+    for key, val in params.items():
+        parts.append(MultipartParam(key, val))
+
+    for paramname, filename in files.items():
+        parts.append(MultipartParam.from_file(paramname=paramname,
+                                              filename=filename))
+
+    body, headers = multipart_encode(parts)
+    return body, headers
+
+
+def _make_form_request(call, pos_args, opts):
+    """Return URL from call and argumewnts, and headers for urlencoding."""
+    form = dict()
+    form["call"] = call
+
+    params = _make_params(pos_args, opts)
+    for key, val in params.items():
+        form[key] = val
+
+    headers = {}
+    headers["Content-Type"] = "application/x-www-form-urlencoded"
+    return urlencode(form), headers
+
+
+def _make_request(call, pos_args, opts, files=None):
+    """
+    Multipart if with files, otherwise url-encoded.
+
+    Return
+    ------
+    path_segments, body, headers
+    """
+
+    if files is not None:
+        return _make_multipart_request(call, pos_args, opts, files)
+
+    return _make_form_request(call, pos_args, opts)
+
+
+def run_server_side_script(call, *args, files=None, **kwargs):
+    """
+
+    Return
+    ------
+    response : ScriptingResponse
+    """
+    body, headers = _make_request(call=call, pos_args=args,
+                                  opts=kwargs, files=files)
+    response = get_connection()._http_request(method="POST",
+                                              path=quote("scripting"),
+                                              body=body,
+                                              headers=headers)
+    xml = etree.parse(response)
+    code = int(xml.xpath("/Response/script/@code")[0])
+    call = xml.xpath("/Response/script/call")[0].text
+    stdout = xml.xpath("/Response/script/stdout")[0].text
+    stderr = xml.xpath("/Response/script/stderr")[0].text
+
+    return ScriptingResponse(call=call,
+                             code=code,
+                             stdout=stdout,
+                             stderr=stderr)
+
+
+class ScriptingResponse():
+    """ScriptingResponse
+
+    A data class for the response of server-side scripting calls.
+
+    Properties
+    ----------
+    code : int
+        The return code of the script process.
+    call : str
+        The complete call of the script minus the absolute path and the
+        auth_token.
+    stdout : str
+        The STDOUT of the script process.
+    stderr : str
+        The STDERR of the script process.
+
+    """
+
+    def __init__(self, call, code, stdout, stderr):
+        self.call = call
+        self.code = code
+        self.stdout = stdout
+        self.stderr = stderr
diff --git a/unittests/test_add_property.py b/unittests/test_add_property.py
index 874314ee62d5471a9e859d8c4dc44c4e4a6532c7..5bae6c219732f0170f5c351eae58148c9d3d065a 100644
--- a/unittests/test_add_property.py
+++ b/unittests/test_add_property.py
@@ -5,6 +5,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 <f.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
@@ -21,69 +23,82 @@
 #
 # ** end header
 #
-import caosdb as db
 from pytest import raises
-from nose.tools import assert_is, assert_is_none, assert_equals, assert_is_not_none, assert_raises
+import caosdb as db
 
 
 def test_no_parameter():
     rec = db.Record()
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
 
-    with assert_raises(UserWarning) as cm:
+    with raises(UserWarning) as cm:
         rec.add_property()
-    assert_equals(
-        cm.exception.args[0],
-        "This method expects you to pass at least an entity, a name or an id.")
-    assert_equals(0, len(rec.get_properties()))
+    assert cm.value.args[0] == ("This method expects you to pass at "
+                                "least an entity, a name or an id.")
+    assert 0 == len(rec.get_properties())
 
 
 def test_only_value_parameter():
     rec = db.Record()
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
 
-    with assert_raises(UserWarning) as cm:
+    with raises(UserWarning) as cm:
         rec.add_property(value="bla")
-    assert_equals(
-        cm.exception.args[0],
-        "This method expects you to pass at least an entity, a name or an id.")
-    assert_equals(0, len(rec.get_properties()))
+    assert cm.value.args[0] == ("This method expects you to pass at "
+                                "least an entity, a name or an id.")
+    assert 0 == len(rec.get_properties())
 
 
 def test_property_name_ambiguity_1():
     rec = db.Record()
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
 
-    with assert_raises(UserWarning) as cm:
+    with raises(UserWarning) as cm:
         rec.add_property("one_name", name="another_name")
-    assert_equals(
-        cm.exception.args[0],
-        "The first parameter was neither an instance of Entity nor an integer. Therefore the string representation of your first parameter would normally be interpreted name of the property which is to be added. But you have also specified a parameter 'name' in the method call. This is ambiguous and cannot be processed.")
-    assert_equals(0, len(rec.get_properties()))
+    assert cm.value.args[0] == ("The first parameter was neither an "
+                                "instance of Entity nor an integer. "
+                                "Therefore the string representation of "
+                                "your first parameter would normally be "
+                                "interpreted name of the property which "
+                                "is to be added. But you have also "
+                                "specified a parameter 'name' in the "
+                                "method call. This is ambiguous and "
+                                "cannot be processed.")
+    assert 0 == len(rec.get_properties())
 
 
 def test_property_name_ambiguity_2():
     rec = db.Record()
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
 
-    with assert_raises(UserWarning) as cm:
+    with raises(UserWarning) as cm:
         rec.add_property({}, name="another_name")
-    assert_equals(
-        cm.exception.args[0],
-        "The first parameter was neither an instance of Entity nor an integer. Therefore the string representation of your first parameter would normally be interpreted name of the property which is to be added. But you have also specified a parameter 'name' in the method call. This is ambiguous and cannot be processed.")
-    assert_equals(0, len(rec.get_properties()))
+    assert cm.value.args[0] == ("The first parameter was neither an "
+                                "instance of Entity nor an integer. "
+                                "Therefore the string representation of "
+                                "your first parameter would normally be "
+                                "interpreted name of the property which "
+                                "is to be added. But you have also "
+                                "specified a parameter 'name' in the "
+                                "method call. This is ambiguous and "
+                                "cannot be processed.")
+    assert 0 == len(rec.get_properties())
 
 
 def test_property_id_ambiguity():
     rec = db.Record()
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
 
-    with assert_raises(UserWarning) as cm:
+    with raises(UserWarning) as cm:
         rec.add_property(25, id=26)
-    assert_equals(
-        cm.exception.args[0],
-        "The first parameter was an integer which would normally be interpreted as the id of the property which is to be added. But you have also specified a parameter 'id' in the method call. This is ambiguous and cannot be processed.")
-    assert_equals(0, len(rec.get_properties()))
+
+    assert cm.value.args[0] == ("The first parameter was an integer which "
+                                "would normally be interpreted as the id of "
+                                "the property which is to be added. But you "
+                                "have also specified a parameter 'id' in the "
+                                "method call. This is ambiguous and cannot be "
+                                "processed.")
+    assert 0 == len(rec.get_properties())
 
 
 def test_property_parameter_with_entity():
@@ -95,18 +110,17 @@ def test_property_parameter_with_entity():
         unit="m",
         description="This is the length of something.")
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(abstract_property)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property("length")
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.name, "length")
-    assert_equals(concrete_property.id, 512)
-    assert_equals(concrete_property.description,
-                  "This is the length of something.")
-    assert_equals(concrete_property.unit, "m")
-    assert_equals(concrete_property.datatype, db.DOUBLE)
-    assert_is(concrete_property._wrapped_entity, abstract_property)
+    assert concrete_property is not None
+    assert concrete_property.name == "length"
+    assert concrete_property.id == 512
+    assert concrete_property.description == "This is the length of something."
+    assert concrete_property.unit == "m"
+    assert concrete_property.datatype == db.DOUBLE
+    assert concrete_property._wrapped_entity == abstract_property
 
 
 def test_property_parameter_with_entity_and_value():
@@ -118,54 +132,53 @@ def test_property_parameter_with_entity_and_value():
         unit="m",
         description="This is the length of something.")
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(abstract_property, 3.14)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property("length")
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.name, "length")
-    assert_equals(concrete_property.id, 512)
-    assert_equals(concrete_property.description,
-                  "This is the length of something.")
-    assert_equals(concrete_property.unit, "m")
-    assert_equals(concrete_property.value, 3.14)
-    assert_equals(concrete_property.datatype, db.DOUBLE)
-    assert_is(concrete_property._wrapped_entity, abstract_property)
+    assert concrete_property is not None
+    assert concrete_property.name == "length"
+    assert concrete_property.id == 512
+    assert concrete_property.description == "This is the length of something."
+    assert concrete_property.unit == "m"
+    assert concrete_property.value == 3.14
+    assert concrete_property.datatype == db.DOUBLE
+    assert concrete_property._wrapped_entity == abstract_property
 
 
 def test_property_parameter_with_id():
     rec = db.Record()
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(512)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property(512)
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.id, 512)
+    assert concrete_property is not None
+    assert concrete_property.id == 512
 
 
 def test_property_parameter_with_id_and_value():
     rec = db.Record()
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(512, 3.14)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property(512)
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.id, 512)
-    assert_equals(concrete_property.value, 3.14)
+    assert concrete_property is not None
+    assert concrete_property.id == 512
+    assert concrete_property.value == 3.14
 
 
 def test_datatype():
     rec = db.Record()
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(512, 3.14)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property(512)
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.id, 512)
-    assert_equals(concrete_property.value, 3.14)
+    assert concrete_property is not None
+    assert concrete_property.id == 512
+    assert concrete_property.value == 3.14
 
 
 def test_property_parameter_with_entity_and_datatype():
@@ -214,23 +227,23 @@ def test_property_parameter_with_entity_and_datatype():
 def test_kw_name_and_value():
     rec = db.Record()
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(name="length", value=3.14)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property("length")
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.value, 3.14)
+    assert concrete_property is not None
+    assert concrete_property.value == 3.14
 
 
 def test_kw_id_and_value():
     rec = db.Record()
 
-    assert_equals(0, len(rec.get_properties()))
+    assert 0 == len(rec.get_properties())
     rec.add_property(id=512, value=3.14)
-    assert_equals(1, len(rec.get_properties()))
+    assert 1 == len(rec.get_properties())
     concrete_property = rec.get_property(512)
-    assert_is_not_none(concrete_property)
-    assert_equals(concrete_property.value, 3.14)
+    assert concrete_property is not None
+    assert concrete_property.value == 3.14
 
 
 def test_add_list_of_entitities():
@@ -240,7 +253,7 @@ def test_add_list_of_entitities():
         values.append(db.Record(name=str(i)))
     rec.add_property("listOfEntities", values)
     for e in rec.get_property("listOfEntities").value:
-        assert_is_none(e.id)
+        assert e.id is None
 
     i = 0
     for val in values:
@@ -249,5 +262,5 @@ def test_add_list_of_entitities():
 
     i = 0
     for e in rec.get_property("listOfEntities").value:
-        assert_equals(i, e.id)
+        assert i == e.id
         i += 1
diff --git a/unittests/test_authentication_auth_token.py b/unittests/test_authentication_auth_token.py
new file mode 100644
index 0000000000000000000000000000000000000000..1eaf091863e23205c9ffca5373e51b654a5a42e4
--- /dev/null
+++ b/unittests/test_authentication_auth_token.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+#
+# ** 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
+#
+"""test_authentication_auth_token
+
+Unit tests for the module caosdb.connection.authentication.auth_token
+"""
+
+from __future__ import unicode_literals
+from pytest import raises
+from unittest.mock import Mock
+from caosdb.connection.authentication import auth_token as at
+from caosdb.connection.mockup import MockUpServerConnection, MockUpResponse
+from caosdb.connection.utils import parse_auth_token
+from caosdb.exceptions import LoginFailedException
+from caosdb import configure_connection
+
+
+def test_get_authentication_provider():
+    ap = at.get_authentication_provider()
+    assert isinstance(ap, at.AuthTokenAuthenticator)
+
+
+def response_with_auth_token():
+    token = "SessionToken=[response token];"
+    assert parse_auth_token(token) is not None, "cookie not ok"
+
+    return MockUpResponse(200, {"Set-Cookie": token}, "ok")
+
+
+def test_configure_connection():
+    def request_has_auth_token(**kwargs):
+        """test resources"""
+        cookie = kwargs["headers"]["Cookie"]
+        assert cookie is not None
+        assert cookie == "SessionToken=%5Brequest%20token%5D;"
+
+        return response_with_auth_token()
+
+    c = configure_connection(password_method="auth_token",
+                             auth_token="[request token]",
+                             implementation=MockUpServerConnection)
+    assert isinstance(c._authenticator, at.AuthTokenAuthenticator)
+
+    c._delegate_connection.resources.append(request_has_auth_token)
+    assert c._authenticator.auth_token == "[request token]"
+    response = c._http_request(method="GET", path="test")
+    assert response.read() == "ok"
+    assert c._authenticator.auth_token == "[response token]"
+
+
+def test_login_raises():
+    c = configure_connection(url="https://example.com",
+                             password_method="auth_token",
+                             auth_token="[auth_token]")
+    with raises(LoginFailedException):
+        c._login()
+
+
+def test_logout_calls_delete():
+    mock = Mock()
+
+    def logout_resource(**kwargs):
+        """logout with auth_token"""
+        mock.method()
+        assert kwargs["path"] == "logout"
+        assert kwargs["method"] == "DELETE"
+
+        cookie = kwargs["headers"]["Cookie"]
+        assert cookie is not None
+        assert cookie == "SessionToken=%5Brequest%20token%5D;"
+
+        return MockUpResponse(200, {}, "ok")
+
+    c = configure_connection(password_method="auth_token",
+                             auth_token="[request token]",
+                             implementation=MockUpServerConnection)
+
+    c._delegate_connection.resources.append(logout_resource)
+    c._logout()
+    mock.method.assert_called_once()
diff --git a/unittests/test_authentication_unauthenticated.py b/unittests/test_authentication_unauthenticated.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ea864a9999c5e3a74fa22fd3f6942c4e5806256
--- /dev/null
+++ b/unittests/test_authentication_unauthenticated.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+#
+# ** 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
+#
+"""test_authentication_unauthenticated
+
+Unit tests for the module caosdb.connection.authentication.unauthenticated.
+"""
+
+from __future__ import unicode_literals
+from pytest import raises
+from unittest.mock import Mock
+from caosdb.connection.authentication import unauthenticated
+from caosdb.connection.mockup import MockUpServerConnection, MockUpResponse
+from caosdb.connection.utils import parse_auth_token
+from caosdb.exceptions import LoginFailedException
+from caosdb import configure_connection
+from .test_authentication_auth_token import response_with_auth_token
+
+
+def test_get_authentication_provider():
+    ap = unauthenticated.get_authentication_provider()
+    assert isinstance(ap, unauthenticated.Unauthenticated)
+
+
+def test_configure_connection():
+    mock = Mock()
+
+    def request_has_no_auth_token(**kwargs):
+        """test resource"""
+        assert "Cookie" not in kwargs["headers"]
+        mock.method()
+        return response_with_auth_token()
+
+    c = configure_connection(password_method="unauthenticated",
+                             implementation=MockUpServerConnection)
+    assert isinstance(c._authenticator, unauthenticated.Unauthenticated)
+
+    c._delegate_connection.resources.append(request_has_no_auth_token)
+
+    assert c._authenticator.auth_token is None
+    response = c._http_request(method="GET", path="test")
+    assert response.read() == "ok"
+    mock.method.assert_called_once()
+    assert c._authenticator.auth_token is None
+
+
+def test_login_raises():
+    c = configure_connection(url="https://example.com",
+                             password_method="unauthenticated")
+    with raises(LoginFailedException):
+        c._login()
diff --git a/unittests/test_connection.py b/unittests/test_connection.py
index c1f62088c425b2ef587a6b0ca0028f38180ba2ee..5c1518c65b51087ace514ca26fa77eff53130c73 100644
--- a/unittests/test_connection.py
+++ b/unittests/test_connection.py
@@ -261,9 +261,27 @@ def test_missing_auth_method():
 def test_missing_password():
     connection = configure_connection()
     connection.configure(implementation=setup_two_resources,
-                         password_method="plain", auth_token="[test-auth-token]")
+                         password_method="plain")
+    connection._authenticator.auth_token = "[test-auth-token]"
     assert connection.retrieve(["some"]).headers["Cookie"] == "SessionToken=%5Btest-auth-token%5D;"
+
     connection.configure(implementation=setup_two_resources,
                          password_method="plain")
     with raises(LoginFailedException):
         connection.delete(["401"])
+
+
+def test_auth_token_connection():
+    connection = configure_connection(auth_token="blablabla",
+                                      password_method="auth_token",
+                                      implementation=setup_two_resources)
+    connection.retrieve(["some"]).headers["Cookie"] == "SessionToken=blablabla;"
+
+    connection._logout()
+    with raises(LoginFailedException) as cm:
+        connection.retrieve(["some"]).headers["Cookie"] == "SessionToken=blablabla;"
+    assert cm.value.args[0] == ("The authentication token is expired or you "
+                                "have been logged out otherwise. The "
+                                "auth_token authenticator cannot log in "
+                                "again. You must provide a new authentication "
+                                "token.")
diff --git a/unittests/test_record.py b/unittests/test_record.py
index 3b4eb4bbd34110706f7b0b1e88daac6c070dc1c6..2738d79265a3d0845f0296bd2da548559244bee1 100644
--- a/unittests/test_record.py
+++ b/unittests/test_record.py
@@ -24,9 +24,7 @@
 #
 """Tests for the Record class."""
 # pylint: disable=missing-docstring
-import unittest
-
-from caosdb import Entity, Record
+from caosdb import Entity, Record, RecordType
 
 
 def test_is_entity():
@@ -39,11 +37,42 @@ def test_role():
     assert record.role == "Record"
 
 
-class TestRecord(unittest.TestCase):
-    def test_property_access(self):
-        rec = Record()
-        rec.add_property("Prop")
-        assert rec.get_property("Pop") is None
-        assert rec.get_property("Prop") is not None
-        assert rec.get_property("prop") is not None
-        assert rec.get_property("prOp") is not None
+def test_property_access():
+    rec = Record(id=123)
+    rec.add_property("Prop")
+    assert rec.get_property("Pop") is None
+    assert rec.get_property("Prop") is not None
+    assert rec.get_property("prop") is not None
+    assert rec.get_property("prOp") is not None
+
+
+def test_get_parent_by_name():
+    rec = Record(id="123")
+    rec.add_parent(name="Test")
+    assert rec.get_parent(None) is None
+    assert rec.get_parent("Not existing") is None
+    assert rec.get_parent("Test") is not None
+    assert rec.get_parent("test") is not None
+    assert rec.get_parent("tEsT") is not None
+
+
+def test_get_parent_by_id():
+    rec = Record(id="123")
+    rec.add_parent(234)
+    assert rec.get_parent(None) is None
+    assert rec.get_parent(234) is not None
+
+
+def test_get_parent_by_entity():
+    rec = Record(id="123")
+    rt = RecordType("Test", id=234)
+    rec.add_parent(rt)
+    assert rec.get_parent(rt) is not None
+
+    rec = Record()
+    rec.add_parent(234)
+    assert rec.get_parent(rt) is not None
+
+    rec = Record()
+    rec.add_parent("Test")
+    assert rec.get_parent(rt) is not None
diff --git a/unittests/test_server_side_scripting.py b/unittests/test_server_side_scripting.py
new file mode 100644
index 0000000000000000000000000000000000000000..952b4e08f52e5031f772eee402cf318a72bf96e0
--- /dev/null
+++ b/unittests/test_server_side_scripting.py
@@ -0,0 +1,98 @@
+import json
+from urllib.parse import parse_qs
+from unittest.mock import Mock
+from caosdb.utils import server_side_scripting as sss
+from caosdb.connection.mockup import MockUpServerConnection, MockUpResponse
+from caosdb import configure_connection
+
+_REMOVE_FILES_AFTERWARDS = []
+
+
+def setup_module():
+    c = configure_connection(password_method="unauthenticated",
+                             implementation=MockUpServerConnection)
+    xml = ('<Response><script code="{code}">'
+           ' <call>{call}</call>'
+           ' <stdout>{stdout}</stdout>'
+           ' <stderr>{stderr}</stderr>'
+           '</script></Response>')
+
+    def scripting_resource(**kwargs):
+        assert kwargs["path"] == "scripting"
+        content_type = kwargs["headers"]["Content-Type"]
+
+        if content_type.startswith("multipart/form-data; boundary"):
+            parts = kwargs["body"]
+            stdout = []
+            for part in parts:
+                if hasattr(part, "decode"):
+                    stdout.append(part.decode("utf-8"))
+                else:
+                    stdout.append(part)
+            stdout = json.dumps(stdout)
+        else:
+            assert content_type == "application/x-www-form-urlencoded"
+            stdout = json.dumps(parse_qs(kwargs["body"].decode("utf-8"),
+                                         encoding="utf-8"))
+        scripting_response = xml.format(code="123",
+                                        call="call string",
+                                        stdout=stdout,
+                                        stderr="stderr string")
+        return MockUpResponse(200, {}, scripting_response)
+    c._delegate_connection.resources.append(scripting_resource)
+
+
+def teardown_module():
+    from os import remove
+    from os.path import exists, isdir
+    from shutil import rmtree
+    for obsolete in _REMOVE_FILES_AFTERWARDS:
+        if exists(obsolete):
+            if isdir(obsolete):
+                rmtree(obsolete)
+            else:
+                remove(obsolete)
+
+
+def test_run_server_side_script():
+    assert type(sss.run_server_side_script).__name__ == "function"
+    r = sss.run_server_side_script("cat", "/etc/passwd", files=None,
+                                   option1="val1")
+    assert r.call == "call string"
+    assert r.code == 123
+    assert r.stderr == "stderr string"
+
+    form = json.loads(r.stdout)
+    assert form["call"] == ["cat"]
+    assert form["-p0"] == ["/etc/passwd"]
+    assert form["-Ooption1"] == ["val1"]
+
+
+def test_run_server_side_script_with_file():
+    _REMOVE_FILES_AFTERWARDS.append("test_file.txt")
+    with open("test_file.txt", "w") as f:
+        f.write("this is a test")
+
+    assert type(sss.run_server_side_script).__name__ == "function"
+    r = sss.run_server_side_script("cat", "/etc/passwd",
+                                   files={"file1": "test_file.txt"},
+                                   option1="val1")
+    assert r.call == "call string"
+    assert r.code == 123
+    assert r.stderr == "stderr string"
+
+    parts = json.loads(r.stdout)
+    print(parts)
+    assert 'name="call"' in parts[0]
+    assert "\r\n\r\ncat\r\n" in parts[0]
+
+    assert 'name="-Ooption1"' in parts[1]
+    assert "\r\n\r\nval1\r\n" in parts[1]
+
+    assert 'name="-p0"' in parts[2]
+    assert "\r\n\r\n/etc/passwd\r\n" in parts[2]
+
+    assert 'name="file1"' in parts[3]
+    assert 'filename="test_file.txt"' in parts[3]
+
+    assert parts[4] == "this is a test"