From d4b09527bbe3ecf281cf0f9083019b79f99809d1 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 12 Mar 2019 10:51:50 +0100 Subject: [PATCH 2/2] templatetags: add token generation with seeds (#31268) - add {% token_decimal/alphanum n seed1 .. seedn %} -> generate token based on hash of concatenation of seed1 .. seedn - using a form linked information (like "form_number") as seed you can generate deterministic unique token for a formdata, removing the need to store the token in a backoffice field of the formdef. --- tests/test_templates.py | 16 ++++++++++++++++ wcs/qommon/templatetags/qommon.py | 21 ++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/tests/test_templates.py b/tests/test_templates.py index b2735797..3f483fa0 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -460,3 +460,19 @@ def test_token_alphanum(): assert not any(set(token) & set('01IiOo') for token in tokens) t = Template('{% if token1|token_check:token2 %}ok{% endif %}') assert t.render({'token1': tokens[0] + ' ', 'token2': tokens[0].lower()}) == 'ok' + + +def test_token_alphanum_with_seed(): + tokens1 = [Template('{% token_alphanum 4 seed1 seed2 %}').render({'seed1': i, 'seed2': i + 1}) for i in range(100)] + tokens2 = [Template('{% token_alphanum 4 seed1 seed2 %}').render({'seed1': i, 'seed2': i + 1}) for i in range(100)] + assert all(len(token) == 4 for token in tokens1) + assert all(token.upper() == token for token in tokens1) + assert all(token.isalnum() for token in tokens1) + assert all(len(token) == 4 for token in tokens2) + assert all(token.upper() == token for token in tokens2) + assert all(token.isalnum() for token in tokens2) + assert tokens1 == tokens2 + # token_check is case insensitive + t = Template('{% if token1|token_check:token2 %}ok{% endif %}') + for token1, token2 in zip(tokens1, tokens2): + assert t.render({'token1': token1 + ' ', 'token2': token2.lower()}) == 'ok' diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py index 95a4e763..cf21a387 100644 --- a/wcs/qommon/templatetags/qommon.py +++ b/wcs/qommon/templatetags/qommon.py @@ -270,27 +270,34 @@ def abs_(value): return decimal(abs(parse_decimal(value))) -def generate_token(alphabet, length): - r = random.SystemRandom() - choices = [r.randrange(len(alphabet)) for i in range(length)] +def generate_token(alphabet, length, *seeds): + if seeds: + choices = [] + seed = ''.join(map(str, seeds)) + for i in range(length): + seed = hashlib.sha256(seed).hexdigest() + choices.append(int(seed, 16) % len(alphabet)) + else: + r = random.SystemRandom() + choices = [r.randrange(len(alphabet)) for i in range(length)] return ''.join([alphabet[choice] for choice in choices]) @register.simple_tag -def token_decimal(length=6): +def token_decimal(length, *seeds): # entropy by default is log(10^6)/log(2) = 19.93 bits # decimal always need more length than alphanum for the same security level # for 128bits security level, length must be more than log(2^128)/log(10) = 38.53 digits - return generate_token(string.digits, length) + return generate_token(string.digits, length, *seeds) @register.simple_tag -def token_alphanum(length=4): +def token_alphanum(length, *seeds): # use of a 28 characters alphabet using uppercase letters and digits but # removing confusing characters and digits 0, O, 1 and I. # entropy by default is log(28^4)/log(2) = 19.22 bits # for 128 bits security level length must be more than log(2^128)/log(28) = 26.62 characters - return generate_token('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', length) + return generate_token('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', length, *seeds) @register.filter -- 2.20.1