Repository: labd/commercetools-python-sdk

Issue:
BUG broken auth missing token
commercetools==5.0.0

got valid creds (works with curl)
creds are present

always dies `*** oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.`

seems related to https://github.com/requests/requests-oauthlib/issues/324

same happens with 4.1.0 so i believe its the interface to the oauthlib that has changed

Comments:

Patch:
diff --git a/CHANGES b/CHANGES
index 8c2726a..3ab1120 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+5.0.1 (2019-11-18)
+------------------
+ - Allow passing in the base auth URL when creating a Client
+
 5.0.0 (2019-11-05)
 ------------------
 This is breaking change since the commercetools api specification is moving
diff --git a/README.rst b/README.rst
index 99c3176..ea2c4d3 100644
--- a/README.rst
+++ b/README.rst
@@ -42,7 +42,7 @@ Example
         client_secret="<your-client-secret>",
         scope=["<scopes>"],
         url="https://api.sphere.io",
-        token_url="https://auth.sphere.io/oauth/token",
+        token_url="https://auth.sphere.io",
     )
 
     product = client.products.get_by_id("00633d11-c5bb-434e-b132-73f7e130b4e3")
diff --git a/src/commercetools/client.py b/src/commercetools/client.py
index 0765893..971323f 100644
--- a/src/commercetools/client.py
+++ b/src/commercetools/client.py
@@ -39,7 +39,7 @@ from commercetools.services.stores import StoreService
 from commercetools.services.subscriptions import SubscriptionService
 from commercetools.services.tax_categories import TaxCategoryService
 from commercetools.services.types import TypeService
-from commercetools.utils import BaseTokenSaver, DefaultTokenSaver
+from commercetools.utils import BaseTokenSaver, DefaultTokenSaver, fix_token_url
 
 
 class RefreshingOAuth2Session(OAuth2Session):
@@ -112,6 +112,7 @@ class Client:
         del project_key, client_id, client_secret, url, token_url, scope
 
         self._config = self._read_env_vars(config)
+        self._config["token_url"] = fix_token_url(self._config["token_url"])
         self._token_saver = token_saver or DefaultTokenSaver()
         self._url = self._config["url"]
         self._base_url = f"{self._config['url']}/{self._config['project_key']}/"
@@ -282,16 +283,6 @@ class Client:
         if not config.get("token_url"):
             config["token_url"] = os.environ.get("CTP_AUTH_URL")
 
-            # When the token_url is passed via environment variables we
-            # check if we need to append /oauth/token to the url. This is
-            # required since commercetools doesn't do this when outputting
-            # the settings when you create an API Client.
-            parts = urllib.parse.urlparse(config["token_url"])
-            if parts.path == "":
-                config["token_url"] = urllib.parse.urlunparse(
-                    (*parts[:2], "/oauth/token", *parts[3:])
-                )
-
         if not config["scope"]:
             config["scope"] = os.environ.get("CTP_SCOPES")
             if config["scope"]:
diff --git a/src/commercetools/utils.py b/src/commercetools/utils.py
index bdf2c09..51caf85 100644
--- a/src/commercetools/utils.py
+++ b/src/commercetools/utils.py
@@ -1,4 +1,5 @@
 import threading
+import urllib.parse
 
 tls = threading.local()
 
@@ -36,3 +37,18 @@ class DefaultTokenSaver(BaseTokenSaver):
     def clear_cache(cls):
         items = getattr(tls, "tokens", {})
         items.clear()
+
+
+def fix_token_url(token_url: str) -> str:
+    """
+    Ensure the token url has the right format.
+
+    Often clients only pass the base url instead of the complete
+    token url, which gets confusing for users.
+    """
+    parts = urllib.parse.urlparse(token_url)
+    if parts.path == "":
+        token_url = urllib.parse.urlunparse(
+            (*parts[:2], "/oauth/token", *parts[3:])
+        )
+    return token_url


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..41ebbdb
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,15 @@
+import pytest
+
+from commercetools.utils import fix_token_url
+
+
+@pytest.mark.parametrize(
+    "token_url,expected_url", [
+        ("https://auth.sphere.io", "https://auth.sphere.io/oauth/token"),
+        ("https://auth.sphere.io/oauth/token", "https://auth.sphere.io/oauth/token"),
+        ("https://auth.commercetools.co", "https://auth.commercetools.co/oauth/token"),
+        ("https://auth.sphere.io?test=123", "https://auth.sphere.io/oauth/token?test=123"),
+    ]
+)
+def test_fix_token_url(token_url, expected_url):
+    assert fix_token_url(token_url) == expected_url