Repository: privacyidea/privacyidea

Issue:
Make splitAtSign consistent
The behaviour of ``splitAtSign`` is not consistent between the endpoint /validate/check and /auth.
We should adapt the endpoint /auth this way, that it behaves in the same was as the endpoint /validate/check.




Discussion:

Resolving patch:
diff --git a/privacyidea/api/auth.py b/privacyidea/api/auth.py
--- a/privacyidea/api/auth.py
+++ b/privacyidea/api/auth.py
@@ -58,7 +58,7 @@
                                   db_admin_exist)
 from privacyidea.lib.user import User, split_user, log_used_user
 from privacyidea.lib.policy import PolicyClass
-from privacyidea.lib.realm import get_default_realm
+from privacyidea.lib.realm import get_default_realm, realm_is_defined
 from privacyidea.api.lib.postpolicy import (postpolicy, get_webui_settings, add_user_detail_to_response, check_tokentype, 
                                             check_tokeninfo, check_serial, no_detail_on_fail, no_detail_on_success,
                                             get_webui_settings)
@@ -121,6 +121,7 @@ def get_auth_token():
         the API.
     :jsonparam password: The password/credentials of the user who wants to
         authenticate to the API.
+    :jsonparam realm: The realm where the user will be searched.
 
     :return: A json response with an authentication token, that needs to be
         used in any further request.
@@ -187,15 +188,30 @@ def get_auth_token():
     validity = timedelta(hours=1)
     username = getParam(request.all_data, "username")
     password = getParam(request.all_data, "password")
-    realm = getParam(request.all_data, "realm")
+    realm_param = getParam(request.all_data, "realm")
     details = {}
+    realm = ''
+
+    # the realm parameter has precedence! Check if it exists
+    if realm_param and not realm_is_defined(realm_param):
+        raise AuthError(_("Authentication failure. Unknown realm: {0!s}.".format(realm_param)),
+                        id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS)
 
     if username is None:
         raise AuthError(_("Authentication failure. Missing Username"),
                         id=ERROR.AUTHENTICATE_MISSING_USERNAME)
 
-    if realm:
-        username = username + "@" + realm
+    loginname = username
+    split_at_sign = get_from_config(SYSCONF.SPLITATSIGN, return_bool=True)
+    if split_at_sign:
+        (loginname, realm) = split_user(username)
+
+    # overwrite the splitted realm if we have a realm parameter
+    if realm_param:
+        realm = realm_param
+
+    # and finaly check if there is a realm
+    realm = realm or get_default_realm()
 
     # Failsafe to have the user attempt in the log, whatever happens
     # This can be overwritten later
@@ -216,9 +232,6 @@ def get_auth_token():
     admin_auth = False
     user_auth = False
 
-    loginname, realm = split_user(username)
-    realm = realm or get_default_realm()
-
     user_obj = User()
 
     # Check if the remote user is allowed
@@ -280,7 +293,7 @@ def get_auth_token():
                                 "resolver": user_obj.resolver,
                                 "serial": details.get('serial', None),
                                 "info": u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj),
-                                        details.get("loginmode"))})
+                                                                        details.get("loginmode"))})
         else:
             g.audit_object.log({"user": user_obj.login,
                                 "realm": user_obj.realm,
diff --git a/privacyidea/api/lib/postpolicy.py b/privacyidea/api/lib/postpolicy.py
--- a/privacyidea/api/lib/postpolicy.py
+++ b/privacyidea/api/lib/postpolicy.py
@@ -54,9 +54,9 @@
 import re
 import netaddr
 from privacyidea.lib.crypto import Sign
-from privacyidea.api.lib.utils import get_all_params, getParam
+from privacyidea.api.lib.utils import get_all_params
 from privacyidea.lib.auth import ROLE
-from privacyidea.lib.user import (split_user, User)
+from privacyidea.lib.user import User
 from privacyidea.lib.realm import get_default_realm
 from privacyidea.lib.subscriptions import subscription_status
 
diff --git a/privacyidea/lib/user.py b/privacyidea/lib/user.py
--- a/privacyidea/lib/user.py
+++ b/privacyidea/lib/user.py
@@ -59,7 +59,7 @@
 from .realm import (get_realms,
                     get_default_realm,
                     get_realm)
-from .config import get_from_config
+from .config import get_from_config, SYSCONF
 from .usercache import (user_cache, cache_username, user_init, delete_user_cache)
 
 log = logging.getLogger(__name__)
@@ -519,7 +519,7 @@ def split_user(username):
     """
     Split the username of the form user@realm into the username and the realm
     splitting myemail@emailprovider.com@realm is also possible and will
-    return (myemail@emailprovider, realm).
+    return (myemail@emailprovider.com, realm).
 
     If for a user@domain the "domain" does not exist as realm, the name is
     not split, since it might be the user@domain in the default realm
@@ -568,8 +568,7 @@ def get_user_from_param(param, optionalOrRequired=optional):
     if username is None:
         username = ""
     else:
-        splitAtSign = get_from_config("splitAtSign", default=False,
-                                      return_bool=True)
+        splitAtSign = get_from_config(SYSCONF.SPLITATSIGN, return_bool=True)
         if splitAtSign:
             (username, realm) = split_user(username)
 


Test patch:
diff --git a/tests/test_api_auth.py b/tests/test_api_auth.py
new file mode 100644
--- /dev/null
+++ b/tests/test_api_auth.py
@@ -0,0 +1,421 @@
+# -*- coding: utf-8 -*-
+""" Test for the '/auth' API-endpoint """
+
+from .base import MyApiTestCase
+import mock
+import six
+from privacyidea.lib.config import (set_privacyidea_config,
+                                    SYSCONF)
+from privacyidea.lib.auth import create_db_admin
+
+
+class AuthApiTestCase(MyApiTestCase):
+    def test_01_auth_with_split(self):
+        # By default, splitAtSign should be true
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, True)
+        self.setUp_user_realms()
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test failed auth
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "password": "false"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Wrong credentials',
+                             result['error']['message'], result)
+
+        # test with realm added to user
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius@realm1",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test with realm added to user and unknown realm param
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius@realm1",
+                                                 "realm": "unknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: unknown.',
+                             result['error']['message'], result)
+
+        # test with realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "realm1",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test with broken realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "unknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: unknown.',
+                             result['error']['message'], result)
+
+        # test with empty realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # realm1 should be the default realm
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test with realm parameter and wrong realm added to user
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "cornelius@unknown",
+                                                     "realm": "realm1",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+            if six.PY2:
+                expected = "The user User(login=u'cornelius@unknown', " \
+                           "realm=u'realm1', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='cornelius@unknown', " \
+                           "realm='realm1', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+        # test with wrong realm parameter and wrong realm added to user
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius@unknown",
+                                                 "realm": "anotherunknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: anotherunknown.',
+                             result['error']['message'], result)
+
+        # Now we take it up one notch and add another resolver
+        self.setUp_user_realm3()
+        # The selfservice user does not exist in realm3
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "selfservice@realm3",
+                                                 "realm": "realm1",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # and the other way round
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "selfservice@realm1",
+                                                     "realm": "realm3",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+                self.assertEqual(4031, result['error']['code'], result)
+                self.assertEqual('Authentication failure. Wrong credentials',
+                                 result['error']['message'], result)
+            # the realm will be split from the login name
+            if six.PY2:
+                expected = "The user User(login=u'selfservice', " \
+                           "realm=u'realm3', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='selfservice', " \
+                           "realm='realm3', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+    # And now we do all of the above without the splitAtSign setting
+    def test_02_auth_without_split(self):
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, False)
+        self.setUp_user_realms()
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test failed auth
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "password": "false"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Wrong credentials',
+                             result['error']['message'], result)
+
+        # test with realm added to user. This fails since we do not split
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "cornelius@realm1",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+                self.assertEqual(4031, result['error']['code'], result)
+                self.assertEqual('Authentication failure. Wrong credentials',
+                                 result['error']['message'], result)
+            if six.PY2:
+                expected = "The user User(login=u'cornelius@realm1', " \
+                           "realm=u'realm1', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='cornelius@realm1', " \
+                           "realm='realm1', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+        # test with realm added to user and unknown realm param
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius@realm1",
+                                                 "realm": "unknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: unknown.',
+                             result['error']['message'], result)
+
+        # test with realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "realm1",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test with broken realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "unknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: unknown.',
+                             result['error']['message'], result)
+
+        # test with empty realm parameter
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": "",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # realm1 should be the default realm
+            self.assertEqual('realm1', result['value']['realm'], result)
+
+        # test with realm parameter and wrong realm added to user
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "cornelius@unknown",
+                                                     "realm": "realm1",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+            if six.PY2:
+                expected = "The user User(login=u'cornelius@unknown', " \
+                           "realm=u'realm1', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='cornelius@unknown', " \
+                           "realm='realm1', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+        # test with wrong realm parameter and wrong realm added to user
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius@unknown",
+                                                 "realm": "anotherunknown",
+                                                 "password": "test"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(401, res.status_code, res)
+            result = res.json.get("result")
+            self.assertFalse(result.get("status"), result)
+            self.assertEqual(4031, result['error']['code'], result)
+            self.assertEqual('Authentication failure. Unknown realm: anotherunknown.',
+                             result['error']['message'], result)
+
+        # Now we take it up one notch and add another resolver
+        self.setUp_user_realm3()
+        # The selfservice user does not exist in realm3
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "selfservice@realm3",
+                                                     "realm": "realm1",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+                self.assertEqual(4031, result['error']['code'], result)
+            if six.PY2:
+                expected = "The user User(login=u'selfservice@realm3', " \
+                           "realm=u'realm1', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='selfservice@realm3', " \
+                           "realm='realm1', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+        # and the other way round
+        with mock.patch("logging.Logger.error") as mock_log:
+            with self.app.test_request_context('/auth',
+                                               method='POST',
+                                               data={"username": "selfservice@realm1",
+                                                     "realm": "realm3",
+                                                     "password": "test"}):
+                res = self.app.full_dispatch_request()
+                self.assertEqual(401, res.status_code, res)
+                result = res.json.get("result")
+                self.assertFalse(result.get("status"), result)
+                self.assertEqual(4031, result['error']['code'], result)
+                self.assertEqual('Authentication failure. Wrong credentials',
+                                 result['error']['message'], result)
+            # the realm will be split from the login name
+            if six.PY2:
+                expected = "The user User(login=u'selfservice@realm1', " \
+                           "realm=u'realm3', resolver='') exists in NO resolver."
+            else:
+                expected = "The user User(login='selfservice@realm1', " \
+                           "realm='realm3', resolver='') exists in NO resolver."
+            mock_log.assert_called_once_with(expected)
+
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, True)
+
+    def test_03_admin_auth(self):
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, True)
+        # check that the default admin works
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "testadmin",
+                                                 "password": "testpw"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # role should be 'admin'
+            self.assertEqual('admin', result['value']['role'], result)
+
+        # add an admin with an '@' in the login name
+        create_db_admin(self.app, 'super@intern', password='testing')
+        # as long as the part after the '@' does not resemble an existing realm,
+        # this should work with 'spltAtSign' set to True
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "super@intern",
+                                                 "password": "testing"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # role should be 'admin'
+            self.assertEqual('admin', result['value']['role'], result)
+
+        # both admin logins should also work with 'splitAtSign' set to False
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, False)
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "testadmin",
+                                                 "password": "testpw"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # role should be 'admin'
+            self.assertEqual('admin', result['value']['role'], result)
+
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "super@intern",
+                                                 "password": "testing"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("status"), result)
+            self.assertIn('token', result.get("value"), result)
+            # role should be 'admin'
+            self.assertEqual('admin', result['value']['role'], result)
+
+        # reset 'splitAtSign' to default value
+        set_privacyidea_config(SYSCONF.SPLITATSIGN, True)
diff --git a/tests/test_api_validate.py b/tests/test_api_validate.py
--- a/tests/test_api_validate.py
+++ b/tests/test_api_validate.py
@@ -747,22 +747,6 @@ def test_05_check_serial_with_no_user(self):
             self.assertEqual(result.get("status"), True)
             self.assertEqual(result.get("value"), True)
 
-    def test_05_check_serial_with_no_user(self):
-        # Check a token per serial when the token has no user assigned.
-        init_token({"serial": "nouser",
-                    "otpkey": self.otpkey,
-                    "pin": "pin"})
-        with self.app.test_request_context('/validate/check',
-                                           method='POST',
-                                           data={"serial": "nouser",
-                                                 "pass": "pin359152"}):
-            res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
-            result = res.json.get("result")
-            details = res.json.get("detail")
-            self.assertEqual(result.get("status"), True)
-            self.assertEqual(result.get("value"), True)
-
     def test_05a_check_otp_only(self):
         # Check the OTP of the token without PIN
         init_token({"serial": "otponly",
diff --git a/tests/test_lib_user.py b/tests/test_lib_user.py
--- a/tests/test_lib_user.py
+++ b/tests/test_lib_user.py
@@ -10,6 +10,7 @@
 
 from .base import MyTestCase
 from privacyidea.lib.resolver import (save_resolver, delete_resolver)
+from privacyidea.lib.config import set_privacyidea_config
 from privacyidea.lib.realm import (set_realm, delete_realm)
 from privacyidea.lib.user import (User, create_user,
                                   get_username,
@@ -17,7 +18,7 @@
                                   split_user,
                                   get_user_from_param)
 from . import ldap3mock
-from .test_lib_resolver import objectGUIDs, LDAPDirectory_small
+from .test_lib_resolver import LDAPDirectory_small
 
 
 class UserTestCase(MyTestCase):
@@ -167,8 +168,10 @@ def test_08_split_user(self):
         # The user is not split, since there is no real "non_existing_realm.com"
         user = split_user("user@non_existing_realm.com")
         self.assertEqual(user, ("user@non_existing_realm.com", ""))
-        
+
     def test_09_get_user_from_param(self):
+        # enable splitAtSign
+        set_privacyidea_config("splitAtSign", True)
         user = get_user_from_param({"user": "cornelius"})
         self.assertTrue(user.realm == self.realm1, user)
         self.assertTrue(user.resolver == self.resolvername1, user)
@@ -199,7 +202,37 @@ def test_09_get_user_from_param(self):
                  "realm": self.realm2}
         user = get_user_from_param(param)
         self.assertEqual("{0!s}".format(user), "<cornelius.resolver1@realm2>")
-        
+
+        # test with splitAtSign set to False
+        set_privacyidea_config("splitAtSign", False)
+
+        # don't split at @, realm will be default realm
+        param = {"user": "cornelius@realm2"}
+        user = get_user_from_param(param)
+        self.assertEqual(user.login, "cornelius@realm2", user)
+        self.assertEqual(user.realm, "realm1", user)
+
+        param = {"user": "cornelius",
+                 "realm": self.realm2}
+        user = get_user_from_param(param)
+        self.assertEqual(user.login, "cornelius", user)
+        self.assertEqual(user.realm, "realm2", user)
+
+        param = {"user": "cornelius@unknown@realm1",
+                 "realm": self.realm2}
+        user = get_user_from_param(param)
+        self.assertEqual(user.login, "cornelius@unknown@realm1", user)
+        self.assertEqual(user.realm, "realm2", user)
+
+        param = {"user": "cornelius//realm1",
+                 "realm": self.realm2}
+        user = get_user_from_param(param)
+        self.assertEqual(user.login, "cornelius//realm1", user)
+        self.assertEqual(user.realm, "realm2", user)
+
+        # reset splitAtSign setting
+        set_privacyidea_config("splitAtSign", True)
+
     def test_10_check_user_password(self):
         (added, failed) = set_realm("passwordrealm",
                                     [self.resolvername3])
@@ -225,8 +258,7 @@ def test_11_get_search_fields(self):
         resolver_sF = sF.get(self.resolvername1)
         self.assertTrue("username" in resolver_sF, resolver_sF)
         self.assertTrue("userid" in resolver_sF, resolver_sF)
-        
-        
+
     def test_12_resolver_priority(self):
         # Test the priority of resolvers.
         # we create resolvers with the same user in it. Depending on the
@@ -351,8 +383,8 @@ def test_16_ordered_resolver(self):
         delete_realm("sort_realm")
 
     def test_17_check_nonascii_user(self):
-        realm = "sqlrealm"
-        resolver = "SQL1"
+        realm = u"sqlrealm"
+        resolver = u"SQL1"
         parameters = self.parameters
         parameters["resolver"] = resolver
         parameters["type"] = "sqlresolver"
@@ -366,7 +398,7 @@ def test_17_check_nonascii_user(self):
 
         # check non-ascii password of non-ascii user
         self.assertFalse(User(login=u"nönäscii",
-                             realm=realm).check_password("wrong"))
+                              realm=realm).check_password("wrong"))
         self.assertTrue(User(login=u"nönäscii",
                              realm=realm).check_password(u"sömepassword"))
 
@@ -379,6 +411,16 @@ def test_17_check_nonascii_user(self):
             self.assertEqual(six.text_type(user_object), u'<nönäscii.SQL1@sqlrealm>')
             self.assertEqual(six.text_type(user_object).encode('utf8'),
                              b'<n\xc3\xb6n\xc3\xa4scii.SQL1@sqlrealm>')
+        # also check the User object representation
+        user_repr = repr(user_object)
+        if six.PY2:
+            self.assertEqual("User(login=u'n\\xf6n\\xe4scii', "
+                             "realm=u'sqlrealm', resolver=u'SQL1')",
+                             user_repr, user_repr)
+        else:
+            self.assertEqual("User(login='nönäscii', "
+                             "realm='sqlrealm', resolver='SQL1')",
+                             user_repr, user_repr)
 
     @ldap3mock.activate
     def test_18_user_with_several_phones(self):