Repository: privacyidea/privacyidea

Issue:
Authentication fails if supplied OTP PIN contains non-ASCII characters
I set up one user ``weber`` (LDAP resolver) and assigned a SPASS token with OTP PIN ``haha``. Then, authentication requests fail if the supplied OTP PIN contains non-ASCII characters:

<pre>
$ curl 'http://localhost:5000/validate/check?user=weber&pass=hühü' | json_pp
{
   "time" : 1501755322.07577,
   "result" : {
      "error" : {
         "message" : "'ascii' codec can't encode character u'\\xfc' in position 1: ordinal not in range(128)",
         "code" : -500
      },
      "status" : false
   },
   "version" : "privacyIDEA unknown",
   "signature" : "...",
   "id" : 1,
   "jsonrpc" : "2.0",
   "detail" : null
}
</pre>

The log says:

<pre>
[2017-08-03 12:15:21,963][9094][140501204362304][ERROR][privacyidea.app:1423] Exception on /validate/check [GET]
Traceback (most recent call last):
...
  File "/home/fred/dev/netknights/privacyidea/privacyidea/privacyidea/lib/tokenclass.py", line 406, in check_pin
    res = self.token.check_pin(pin)
  File "/home/fred/dev/netknights/privacyidea/privacyidea/privacyidea/models.py", line 380, in check_pin
    mypHash = self.get_hashed_pin(pin)
  File "/home/fred/dev/netknights/privacyidea/privacyidea/privacyidea/models.py", line 338, in get_hashed_pin
    self.pin_seed))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 1: ordinal not in range(128)
</pre>


Discussion:
The relevant line is:
```python
log.debug("hPin: {0!s}, pin: {1!s}, seed: {2!s}".format(binascii.hexlify(hPin),
                                           pin,
                                           self.pin_seed))
```
which fails because ``pin`` is ``unicode`` which gets implicitly converted to ``bytestring`` as long as it only contains ASCII characters. If I replace ``"... pin: {1!s} ..."`` with ``"... pin: {1!r} ..."``, everything is fine and authentication works for both ASCII and non-ASCII PINs.

We could check the code for more occurrences of that pattern (unicode string inserted into bytestring formatstring).

Resolving patch:
diff --git a/privacyidea/models.py b/privacyidea/models.py
--- a/privacyidea/models.py
+++ b/privacyidea/models.py
@@ -333,7 +333,7 @@ def get_hashed_pin(self, pin):
         seed_str = self._fix_spaces(self.pin_seed)
         seed = binascii.unhexlify(seed_str)
         hPin = hash(pin, seed)
-        log.debug("hPin: {0!s}, pin: {1!s}, seed: {2!s}".format(binascii.hexlify(hPin),
+        log.debug("hPin: {0!s}, pin: {1!r}, seed: {2!s}".format(binascii.hexlify(hPin),
                                                    pin,
                                                    self.pin_seed))
         return binascii.hexlify(hPin)


Test patch:
diff --git a/tests/test_lib_token.py b/tests/test_lib_token.py
--- a/tests/test_lib_token.py
+++ b/tests/test_lib_token.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 """
 This test file tests the lib.token methods.
 
@@ -787,6 +788,23 @@ def test_36_check_user_pass(self):
         # 8: '447589', 9: '903435', 10: '578337', 11: '328281',
         # 12: '191635', 13: '184416', 14: '574561', 15: '797908'
 
+    def test_36b_check_nonascii_pin(self):
+        user = User("cornelius", self.realm1)
+        serial = "nonasciipin"
+        token = init_token({"type": "hotp",
+                            "otpkey": self.otpkey,
+                            "pin": u"ünicøde",
+                            "serial": serial}, user)
+        r = check_user_pass(user, u"µröng287082")
+        self.assertEqual(r[0], False)
+        self.assertEqual(r[1]['message'], 'wrong otp pin')
+        r = check_user_pass(user, u"ünicøde287082")
+        self.assertEqual(r[0], True)
+        r = check_user_pass(user, u"ünicøde666666")
+        self.assertEqual(r[0], False)
+        self.assertEqual(r[1]['message'], 'wrong otp value')
+        remove_token(serial)
+
     def test_37_challenge(self):
         # We create a challenge by first sending the PIN of the HOTP token
         # then we answer the challenge by sending the OTP.