Repository: privacyidea/privacyidea

Issue:
%' is not properly decoded by the Privacyidea server
Using credential provider 3.4.0
Using Privacyidea 3.8 or 3.9
Ticket 3554

When there is a % in the password of the user, Authentication failed.
If the % is at the beginning or the end of the password, the authentication is successful.

The issue on the side of the credential provider is fixed since the 3.4
https://github.com/privacyidea/privacyidea-credential-provider/releases/tag/v3.4.0
_Fixed a bug where the '%' was not properly encoded when communicating with Privacyidea.

So it must be the decoding of the password on the side of the server that goes wrong



Discussion:
Add the Credential Provider here:

https://github.com/privacyidea/privacyidea/blob/6b85c6047c7fe7355c01962de8b06532a6548811/privacyidea/api/lib/utils.py#L26-L25

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
@@ -51,7 +51,8 @@
 # TODO: we should probably switch this when we do not do the extra unquote anymore
 NO_UNQUOTE_USER_AGENTS = {
     'privacyIDEA-LDAP-Proxy': None,
-    'simpleSAMLphp': None
+    'simpleSAMLphp': None,
+    'privacyidea-cp': None
 }
 
 SESSION_KEY_LENGTH = 32


Test patch:
diff --git a/tests/test_api_lib_utils.py b/tests/test_api_lib_utils.py
--- a/tests/test_api_lib_utils.py
+++ b/tests/test_api_lib_utils.py
@@ -305,6 +305,15 @@ def test_09_check_unquote(self):
             res = self.app.full_dispatch_request()
             self.assertTrue(res.status_code == 200, res)
 
+        # And check the Credential Provider plugin
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={'username': 'pwpercent',
+                                                 'password': 'pw%45#test'},
+                                           headers={'User-Agent': 'privacyidea-cp/2.0'}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+
         # now check the /validate/check endpoint
         set_policy(name="otppin",
                    scope=SCOPE.AUTH,
@@ -346,6 +355,18 @@ def test_09_check_unquote(self):
             result = res.json.get("result")
             self.assertTrue(result.get("value"), res.json)
 
+        # when using the correct user-agent (CredentialProvider), quoting is not necessary
+        with self.app.test_request_context('/validate/check',
+                                           method='POST',
+                                           data={"user": "pwpercent",
+                                                 "realm": self.realm1,
+                                                 "pass": "pw%45#test"},
+                                           headers={'User-Agent': 'privacyidea-cp/3.0'}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(res.status_code, 200, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("value"), res.json)
+
         # cleanup
         remove_token(serial='spass1d')
         delete_policy('otppin')