From 3067c1e2f00923be45e483846e5ff3b7be61bebd Mon Sep 17 00:00:00 2001
From: Timm Fitschen <timm.fitschen@ds.mpg.de>
Date: Mon, 22 Oct 2018 22:15:25 +0200
Subject: [PATCH] ENH: Authenticator decodes auth tokens now.

... for better support of user-provided auth tokens.
---
 .../connection/authentication/interface.py    |  4 +-
 src/caosdb/connection/utils.py                | 62 ++++++++++++++++---
 unittests/test_connection.py                  |  4 +-
 3 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/src/caosdb/connection/authentication/interface.py b/src/caosdb/connection/authentication/interface.py
index 008933ca..8be18b57 100644
--- a/src/caosdb/connection/authentication/interface.py
+++ b/src/caosdb/connection/authentication/interface.py
@@ -27,7 +27,7 @@ from abc import ABCMeta, abstractmethod, abstractproperty
 import logging
 from caosdb.connection.utils import urlencode
 from caosdb.connection.interface import CaosDBServerConnection
-from caosdb.connection.utils import parse_auth_token
+from caosdb.connection.utils import parse_auth_token, auth_token_to_cookie
 from caosdb.exceptions import LoginFailedException
 
 # meta class compatible with Python 2 *and* 3:
@@ -146,7 +146,7 @@ class AbstractAuthenticator(ABC):
         -------
         """
         if self.auth_token is not None:
-            headers['Cookie'] = self.auth_token
+            headers['Cookie'] = auth_token_to_cookie(self.auth_token)
 
 
 class CredentialsAuthenticator(AbstractAuthenticator):
diff --git a/src/caosdb/connection/utils.py b/src/caosdb/connection/utils.py
index dfee2383..c1245a08 100644
--- a/src/caosdb/connection/utils.py
+++ b/src/caosdb/connection/utils.py
@@ -30,10 +30,11 @@ except ImportError:
 try:  # pragma: no cover
     # python3
     from urllib.parse import (urlencode as _urlencode, quote as _quote,
-                              urlparse, urlunparse)
+                              urlparse, urlunparse, unquote as _unquote)
 except ImportError:  # pragma: no cover
     # python2
-    from urllib import urlencode as _urlencode, quote as _quote
+    from urllib import (urlencode as _urlencode, quote as _quote, unquote as
+                        _unquote)
     from urlparse import urlparse, urlunparse
 import re
 
@@ -163,12 +164,59 @@ def check_python_ssl_version(hexversion):
             "\nPython 3 version is smaller than 3.2. It is not does not fully support SSL encryption. Please update your Python to 2.7.9 or greater, or 3.2 or greater."
         )
 
+_PATTERN = re.compile(r"^SessionToken=([^;]*);.*$")
+
+def unquote(string):
+    """unquote.
+
+    Decode an urlencoded string into a plain text string.
+    """
+    bts = _unquote(string)
+    if hasattr(bts, "decode"):
+        # python 2
+        return bts.decode("utf-8")
+    return bts
 
 def parse_auth_token(cookie):
+    """parse_auth_token.
+
+    Parse an auth token from a cookie.
+
+    Parameters
+    ----------
+    cookie : str
+        A cookie with an urlencoded authtoken.
+
+    Returns
+    -------
+    str
+        An auth token string.
+    """
     auth_token = None
-    if cookie is not None:
-        try:
-            auth_token = re.compile(r";\s*.*$").split(cookie)[0]
-        except IndexError:
-            pass
+    if cookie is not None and _PATTERN.match(cookie):
+        auth_token = unquote(_PATTERN.split(cookie)[1])
     return auth_token
+
+def auth_token_to_cookie(auth_token):
+    """auth_token_to_cookie.
+
+    Urlencode an auth token string and format it as a cookie.
+
+    Parameters
+    ----------
+    auth_token : str
+        The plain auth token string.
+
+    Raises
+    ------
+    TypeError
+        If the auth_token was None
+
+    Returns
+    -------
+    str
+        A cookie
+    """
+    if auth_token is None:
+        raise TypeError("Parameter `auth_token` was None.")
+    return "SessionToken=" + quote(auth_token) + ";"
diff --git a/unittests/test_connection.py b/unittests/test_connection.py
index fb9803c8..06838d1c 100644
--- a/unittests/test_connection.py
+++ b/unittests/test_connection.py
@@ -259,8 +259,8 @@ 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")
-    assert connection.retrieve(["some"]).headers["Cookie"] == "test-auth-token"
+                         password_method="plain", 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):
-- 
GitLab