Repository: privacyidea/privacyidea

Issue:
Content-Type x-www-form-urlencoded for POST not considered
The body of a POST request which has the Content-Type application/x-www-form-urlencoded is url encoded. It seems that it is not considered by the server because the body is not decoded and therefore the requests result in failure.


Discussion:
The problem seems to arise, that we usually expect application/json, which we do not need to decode.

We might need to check whether we receive a request as application/x-www-form-urlencoded and would need to decode these data.

Looks like we have to decode the parameters here:

https://github.com/privacyidea/privacyidea/blob/master/privacyidea/api/lib/utils.py#L246

were we combine all type of paramters (json-body, form-params) to our new dict ``request.all_data``.

Interesting, i just stumbled upon this issue in the tests when updating Flask/Werkzeug. So it might be a good idea to check for `application/x-www-form-urlencoded`.

Resolving patch:
diff --git a/privacyidea/api/lib/utils.py b/privacyidea/api/lib/utils.py
--- a/privacyidea/api/lib/utils.py
+++ b/privacyidea/api/lib/utils.py
@@ -37,6 +37,7 @@
 import threading
 import six
 import re
+from six.moves.urllib.parse import unquote
 from flask import (jsonify,
                    current_app)
 
@@ -243,7 +244,7 @@ def get_all_params(param, body):
     """
     return_param = {}
     for key in param.keys():
-        return_param[key] = param[key]
+        return_param[key] = unquote(param[key])
 
     # In case of serialized JSON data in the body, add these to the values.
     try:


Test patch:
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
@@ -1593,7 +1593,7 @@ def test_15_validate_at_sign(self):
         with self.app.test_request_context('/validate/check',
                                            method='POST',
                                            data={"user":
-                                                    "cornelius@"+self.realm2,
+                                                    "cornelius@" + self.realm2,
                                                  "pass": serial2}):
             res = self.app.full_dispatch_request()
             self.assertTrue(res.status_code == 400, res)
@@ -1602,7 +1602,18 @@ def test_15_validate_at_sign(self):
         with self.app.test_request_context('/validate/check',
                                            method='POST',
                                            data={"user":
-                                                    "cornelius@"+self.realm2,
+                                                    "cornelius@" + self.realm2,
+                                                 "pass": serial2}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("value"))
+
+        # Also test url-encoded parameters
+        with self.app.test_request_context('/validate/check',
+                                           method='POST',
+                                           data={"user":
+                                                    "cornelius%40" + self.realm2,
                                                  "pass": serial2}):
             res = self.app.full_dispatch_request()
             self.assertTrue(res.status_code == 200, res)
@@ -1615,7 +1626,7 @@ def test_15_validate_at_sign(self):
         with self.app.test_request_context('/validate/check',
                                            method='POST',
                                            data={"user":
-                                                    "cornelius@"+self.realm2,
+                                                    "cornelius@" + self.realm2,
                                                  "pass": serial2}):
             res = self.app.full_dispatch_request()
             self.assertTrue(res.status_code == 200, res)
@@ -1634,7 +1645,7 @@ def test_16_autoresync_hotp(self):
         with self.app.test_request_context('/validate/check',
                                            method='POST',
                                            data={"user":
-                                                    "cornelius@"+self.realm2,
+                                                    "cornelius@" + self.realm2,
                                                  "pass": "async399871"}):
             res = self.app.full_dispatch_request()
             self.assertEqual(res.status_code, 200)
@@ -1646,7 +1657,7 @@ def test_16_autoresync_hotp(self):
         with self.app.test_request_context('/validate/check',
                                            method='POST',
                                            data={"user":
-                                                    "cornelius@"+self.realm2,
+                                                    "cornelius@" + self.realm2,
                                                  "pass": "async520489"}):
             res = self.app.full_dispatch_request()
             self.assertEqual(res.status_code, 200)