Skip to content

Commit

Permalink
Merge pull request #1415 from privacyidea/python3_tokens_hotp
Browse files Browse the repository at this point in the history
Migrate test_lib_apps.py, HOTP and TOTP tests
  • Loading branch information
plettich committed Feb 4, 2019
2 parents 911ac0d + 0a4db6a commit 4133f5e
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 42 deletions.
25 changes: 12 additions & 13 deletions privacyidea/lib/apps.py
Expand Up @@ -43,32 +43,31 @@
"""

import binascii
import base64

import logging
log = logging.getLogger(__name__)

from six.moves.urllib.parse import quote

from privacyidea.lib.log import log_with
from privacyidea.lib.user import User
from privacyidea.lib.utils import to_byte_string, b32encode_and_unicode

log = logging.getLogger(__name__)
MAX_QRCODE_LEN = 180


def _construct_extra_parameters(extra_data):
"""
Given a dictionary of extra key-value pairs (all unicode strings),
Given a dictionary of extra key-value pairs (all str),
return a string that may be appended to a google authenticator / oathtoken URL.
Keys and values are converted to strings and urlquoted. Unicodes are converted to UTF-8.
:return: a string (may be empty if ``extra_data`` is empty
Values that are non-strings will be converted to str.
Keys and values are converted to UTF-8 and urlquoted.
:return: a string (may be empty if ``extra_data`` is empty)
"""
extra_data_list = []
for key, value in extra_data.items():
if isinstance(key, unicode):
key = key.encode('utf-8')
if isinstance(value, unicode):
value = value.encode('utf-8')
extra_data_list.append('{key}={value}'.format(key=quote(str(key)),
value=quote(str(value))))
encoded_key = quote(to_byte_string(key))
encoded_value = quote(to_byte_string(value))
extra_data_list.append('{key}={value}'.format(key=encoded_key, value=encoded_value))
return ('&' if extra_data_list else '') + '&'.join(extra_data_list)


Expand Down Expand Up @@ -126,7 +125,7 @@ def create_google_authenticator_url(key=None, user=None,

key_bin = binascii.unhexlify(key)
# also strip the padding =, as it will get problems with the google app.
otpkey = base64.b32encode(key_bin).strip('=')
otpkey = b32encode_and_unicode(key_bin).strip('=')

base_len = len("otpauth://{0!s}/?secret={1!s}&counter=1".format(tokentype, otpkey))
allowed_label_len = MAX_QRCODE_LEN - base_len
Expand Down
6 changes: 2 additions & 4 deletions privacyidea/lib/tokens/hotptoken.py
Expand Up @@ -46,7 +46,6 @@

import time
import binascii
import base64

from .HMAC import HmacOtp
from privacyidea.api.lib.utils import getParam
Expand All @@ -59,7 +58,7 @@
from privacyidea.lib.apps import create_google_authenticator_url as cr_google
from privacyidea.lib.error import ParameterError
from privacyidea.lib.apps import create_oathtoken_url as cr_oath
from privacyidea.lib.utils import create_img, is_true
from privacyidea.lib.utils import create_img, is_true, b32encode_and_unicode
from privacyidea.lib.policydecorators import challenge_response_allowed
from privacyidea.lib.decorators import check_token_locked
from privacyidea.lib.auth import ROLE
Expand Down Expand Up @@ -226,8 +225,7 @@ def get_init_detail(self, params=None, user=None):
try:
key_bin = binascii.unhexlify(otpkey)
# also strip the padding =, as it will get problems with the google app.
value_b32 = base64.b32encode(key_bin).strip('=')
value_b32_str = "{0!s}".format(value_b32)
value_b32_str = b32encode_and_unicode(key_bin).strip('=')
response_detail["otpkey"]["value_b32"] = value_b32_str
goo_url = cr_google(key=otpkey,
user=user.login,
Expand Down
12 changes: 12 additions & 0 deletions privacyidea/lib/utils.py
Expand Up @@ -205,6 +205,18 @@ def hexlify_and_unicode(s):
return res


def b32encode_and_unicode(s):
"""
Base32-encode a str (which is first encoded to UTF-8)
or a byte string and return the result as a str.
:param s: str or bytes to base32-encode
:type s: str or bytes
:return: base32-encoded string converted to unicode
:rtype: str
"""
return to_unicode(base64.b32encode(to_bytes(s)))


def create_png(data, alt=None):
img = qrcode.make(data)

Expand Down
3 changes: 0 additions & 3 deletions tests/conftest.py
Expand Up @@ -22,21 +22,18 @@
'test_api_users.py',
'test_api_validate.py',
'test_lib_applications.py',
'test_lib_apps.py',
'test_lib_caconnector.py',
'test_lib_challenges.py',
'test_lib_importotp.py',
'test_lib_smsprovider.py',
'test_lib_tokens_certificate.py',
'test_lib_tokens_daplug.py',
'test_lib_tokens_foureyes.py',
'test_lib_tokens_hotp.py',
'test_lib_tokens_motp.py',
'test_lib_tokens_passwordtoken.py',
'test_lib_tokens_radius.py',
'test_lib_tokens_remote.py',
'test_lib_tokens_tiqr.py',
'test_lib_tokens_totp.py',
'test_lib_tokens_u2f.py',
'test_lib_tokens_yubico.py',
'test_lib_tokens_yubikey.py',
Expand Down
18 changes: 9 additions & 9 deletions tests/test_lib_token.py
Expand Up @@ -13,14 +13,14 @@
gettokensoftype
getToken....
"""

from .base import MyTestCase
from privacyidea.lib.user import (User)
from privacyidea.lib.tokenclass import TokenClass, TOKENKIND
from privacyidea.lib.tokens.totptoken import TotpTokenClass
from privacyidea.models import (Token, Challenge, TokenRealm)
from privacyidea.lib.config import (set_privacyidea_config, get_token_types)
from privacyidea.lib.policy import set_policy, SCOPE, ACTION, delete_policy
from privacyidea.lib.utils import b32encode_and_unicode
import datetime
import hashlib
import base64
Expand Down Expand Up @@ -1231,7 +1231,7 @@ def test_50_otpkeyformat(self):
realm=self.realm1))
remove_token("NEW001")
# successful base32check encoding
base32check_encoding = base64.b32encode(checksum + otpkey).strip(b"=").decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey).strip("=")
tokenobject = init_token({"serial": "NEW002", "type": "hotp",
"otpkey": base32check_encoding,
"otpkeyformat": "base32check"},
Expand All @@ -1244,7 +1244,7 @@ def test_50_otpkeyformat(self):
remove_token("NEW002")

# successful base32check encoding, but lower case
base32check_encoding = base64.b32encode(checksum + otpkey).strip(b"=").decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey).strip("=")
base32check_encoding = base32check_encoding.lower()
tokenobject = init_token({"serial": "NEW002", "type": "hotp",
"otpkey": base32check_encoding,
Expand All @@ -1258,7 +1258,7 @@ def test_50_otpkeyformat(self):
remove_token("NEW002")

# base32check encoding with padding
base32check_encoding = base64.b32encode(checksum + otpkey).decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey)
tokenobject = init_token({"serial": "NEW003", "type": "hotp",
"otpkey": base32check_encoding,
"otpkeyformat": "base32check"},
Expand All @@ -1270,7 +1270,7 @@ def test_50_otpkeyformat(self):
binascii.hexlify(otpkey))
remove_token("NEW003")
# invalid base32check encoding (incorrect checksum due to typo)
base32check_encoding = base64.b32encode(checksum + otpkey).decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey)
base32check_encoding = "A" + base32check_encoding[1:]
self.assertRaisesRegexp(ParameterError,
"Incorrect checksum",
Expand All @@ -1281,7 +1281,7 @@ def test_50_otpkeyformat(self):
user=User(login="cornelius", realm=self.realm1))
remove_token("NEW004") # TODO: Token is created anyway?
# invalid base32check encoding (missing four characters => incorrect checksum)
base32check_encoding = base64.b32encode(checksum + otpkey).decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey)
base32check_encoding = base32check_encoding[:-4]
self.assertRaisesRegexp(ParameterError,
"Incorrect checksum",
Expand All @@ -1292,7 +1292,7 @@ def test_50_otpkeyformat(self):
user=User(login="cornelius", realm=self.realm1))
remove_token("NEW005") # TODO: Token is created anyway?
# invalid base32check encoding (too many =)
base32check_encoding = base64.b32encode(checksum + otpkey).decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey)
base32check_encoding = base32check_encoding + "==="
self.assertRaisesRegexp(ParameterError,
"Invalid base32",
Expand All @@ -1303,7 +1303,7 @@ def test_50_otpkeyformat(self):
user=User(login="cornelius", realm=self.realm1))
remove_token("NEW006") # TODO: Token is created anyway?
# invalid base32check encoding (wrong characters)
base32check_encoding = base64.b32encode(checksum + otpkey).decode('utf8')
base32check_encoding = b32encode_and_unicode(checksum + otpkey)
base32check_encoding = "1" + base32check_encoding[1:]
self.assertRaisesRegexp(ParameterError,
"Invalid base32",
Expand All @@ -1314,7 +1314,7 @@ def test_50_otpkeyformat(self):
user=User(login="cornelius", realm=self.realm1))
remove_token("NEW006") # TODO: Token is created anyway?
# invalid key (too short)
base32check_encoding = base64.b32encode(b'Yo').decode('utf8')
base32check_encoding = b32encode_and_unicode(b'Yo')
self.assertRaisesRegexp(ParameterError,
"Too short",
init_token,
Expand Down
10 changes: 6 additions & 4 deletions tests/test_lib_tokens_hotp.py
Expand Up @@ -3,6 +3,7 @@
The lib.tokenclass depends on the DB model and lib.user
"""

PWFILE = "tests/testdata/passwords"

from .base import MyTestCase
Expand All @@ -11,6 +12,7 @@
from privacyidea.lib.realm import (set_realm)
from privacyidea.lib.user import (User)
from privacyidea.lib.tokenclass import DATE_FORMAT
from privacyidea.lib.utils import b32encode_and_unicode
from privacyidea.lib.tokens.hotptoken import HotpTokenClass
from privacyidea.models import (Token,
Config,
Expand Down Expand Up @@ -636,7 +638,7 @@ def test_25_sha256_token(self):
db_token = Token(serial, tokentype="hotp")
db_token.save()
token = HotpTokenClass(db_token)
token.set_otpkey(binascii.hexlify("12345678901234567890"))
token.set_otpkey(binascii.hexlify(b"12345678901234567890"))
token.set_hashlib("sha256")
token.set_otplen(8)
token.save()
Expand Down Expand Up @@ -740,7 +742,7 @@ def test_29_2step_generation_custom(self):
server_component = binascii.unhexlify(token.token.get_otpkey().getKey())
# too short
self.assertRaises(ParameterError, token.update, {
"otpkey": binascii.hexlify("="*8)
"otpkey": binascii.hexlify(b"="*8)
})
# generate a 12-byte client component
client_component = b'abcdefghijkl'
Expand Down Expand Up @@ -786,12 +788,12 @@ def test_30_2step_otpkeyformat(self):
"Incorrect checksum",
token.update,
{
"otpkey": base64.b32encode("\x37" + checksum[1:] + client_component).strip("="),
"otpkey": b32encode_and_unicode(b"\x37" + checksum[1:] + client_component).strip("="),
"otpkeyformat": "base32check",
})
# construct a secret
token.update({
"otpkey": base64.b32encode(checksum + client_component).strip("="),
"otpkey": b32encode_and_unicode(checksum + client_component).strip("="),
"otpkeyformat": "base32check",
# the following values are ignored
"2step_serversize": "23",
Expand Down
20 changes: 11 additions & 9 deletions tests/test_lib_tokens_totp.py
Expand Up @@ -499,11 +499,13 @@ def test_19_pin_otp_functions(self):
#self.assertTrue(res == 47251647, res)
self.assertTrue(res == -1, res)

# simple get_otp of current time
r = token.get_otp()
self.assertTrue(r > 47251648, r)
r = token.get_otp(current_time=datetime.datetime.now())
self.assertTrue(r > 47251648, r)
# simple OTPs of current time
ret, _, dct = token.get_multi_otp(1)
self.assertTrue(ret)
self.assertGreater(list(dct["otp"].keys())[0], 47251648)
ret, _, dct = token.get_multi_otp(1, curTime=datetime.datetime.now())
self.assertTrue(ret)
self.assertGreater(list(dct["otp"].keys())[0], 47251648)

def test_20_check_challenge_response(self):
db_token = Token.query.filter_by(serial=self.serial1).first()
Expand Down Expand Up @@ -645,7 +647,7 @@ def test_25_sha256_token(self):
db_token = Token(serial, tokentype="totp")
db_token.save()
token = TotpTokenClass(db_token)
token.set_otpkey(binascii.hexlify("12345678901234567890"))
token.set_otpkey(binascii.hexlify(b"12345678901234567890"))
token.set_hashlib("sha256")
token.set_otplen(8)
token.save()
Expand All @@ -661,7 +663,7 @@ def test_25_sha256_token(self):
db_token = Token(serial, tokentype="totp")
db_token.save()
token = TotpTokenClass(db_token)
token.set_otpkey(binascii.hexlify("12345678901234567890123456789012"))
token.set_otpkey(binascii.hexlify(b"12345678901234567890123456789012"))
token.set_hashlib("sha256")
token.set_otplen(8)
token.save()
Expand All @@ -675,7 +677,7 @@ def test_25_sha256_token(self):
db_token = Token(serial, tokentype="totp")
db_token.save()
token = TotpTokenClass(db_token)
token.set_otpkey(binascii.hexlify("12345678901234567890"))
token.set_otpkey(binascii.hexlify(b"12345678901234567890"))
token.set_hashlib("sha512")
token.set_otplen(8)
token.save()
Expand All @@ -690,7 +692,7 @@ def test_25_sha256_token(self):
db_token.save()
token = TotpTokenClass(db_token)
token.set_otpkey(binascii.hexlify(
"1234567890123456789012345678901234567890123456789012345678901234"))
b"1234567890123456789012345678901234567890123456789012345678901234"))
token.set_hashlib("sha512")
token.set_otplen(8)
token.save()
Expand Down

0 comments on commit 4133f5e

Please sign in to comment.