Repository: pallets/werkzeug (python)

Issue:
Authorization.from_header with "Token" scheme cannot handle base64 padding on tokens
# Steps to reproduce

```
import base64
from werkzeug.datastructures.auth import Authorization
token = base64.encodebytes(b"This has base64 padding").decode("utf-8").strip()
a = Authorization.from_header(f"Token {token}")
a.token
```

# Expected result

```
VGhpcyBoYXMgYmFzZTY0IHBhZGRpbmc=
```

# Actual behavior

A `NoneType` is returned.

Environment:

- Python version: `Python 3.11.2 (v3.11.2:878ead1ac1, Feb  7 2023, 10:02:41) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin`
- Werkzeug version: 2.3.3 / `main`


Pull request description:
Make werkzeug.http.parse_dict_header avoid base64 padding
Strip off trailing `=` characters before parsing to avoid chopping up tokens.

Fixes #2685


Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 402d3bba..0a37fd89 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,9 @@ Version 2.3.4
 
 Unreleased
 
+-   ``Authorization.from_header`` detects tokens that end with base64 padding (``=``).
+    :issue:`2685`
+
 
 Version 2.3.3
 -------------
diff --git a/src/werkzeug/datastructures/auth.py b/src/werkzeug/datastructures/auth.py
index afc719c7..7d36a7ff 100644
--- a/src/werkzeug/datastructures/auth.py
+++ b/src/werkzeug/datastructures/auth.py
@@ -112,13 +112,12 @@ class Authorization:
 
             return cls(scheme, {"username": username, "password": password})
 
-        parameters = parse_dict_header(rest)
+        if "=" in rest.rstrip("="):
+            # = that is not trailing, this is parameters.
+            return cls(scheme, parse_dict_header(rest), None)
 
-        if len(parameters) == 1 and parameters[next(iter(parameters))] is None:
-            # There is one parameter with no value, was actually a token.
-            return cls(scheme, None, rest)
-
-        return cls(scheme, parameters, None)
+        # No = or only trailing =, this is a token.
+        return cls(scheme, None, rest)
 
     def to_header(self) -> str:
         """Produce an ``Authorization`` header value representing this data.


Test patch:
diff --git a/tests/test_http.py b/tests/test_http.py
index 5ceb7ef5..8f21c12a 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -203,6 +203,19 @@ class TestHTTPUtility:
         assert Authorization.from_header(None) is None
         assert Authorization.from_header("foo").type == "foo"
 
+    def test_authorization_token_padding(self):
+        # padded with =
+        token = base64.b64encode(b"This has base64 padding").decode()
+        a = Authorization.from_header(f"Token {token}")
+        assert a.type == "token"
+        assert a.token == token
+
+        # padded with ==
+        token = base64.b64encode(b"This has base64 padding..").decode()
+        a = Authorization.from_header(f"Token {token}")
+        assert a.type == "token"
+        assert a.token == token
+
     def test_bad_authorization_header_encoding(self):
         """If the base64 encoded bytes can't be decoded as UTF-8"""
         content = base64.b64encode(b"\xffser:pass").decode()