From 224e888a50298f46908eff77f8ef5ed551382ccd 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 | 19 +++++++++++++------ 2 files changed, 29 insertions(+), 6 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 393823ea..ee945c8c 100644 --- a/wcs/qommon/templatetags/qommon.py +++ b/wcs/qommon/templatetags/qommon.py @@ -270,22 +270,29 @@ def abs_(value): return decimal(abs(parse_decimal(value))) -def generate_token(alphabet, length): +def generate_token(alphabet, length, *seeds): # 128bits security is enough; for alphanum, 128 / log(32) = 36.9... if length > 37: raise ValueError('token_decimal/alphanum: length cannot be more than 37') - choices = [random.SystemRandom().randrange(len(alphabet)) for i in range(length)] + 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: + choices = [random.SystemRandom().randrange(len(alphabet)) for i in range(length)] return ''.join([alphabet[choice] for choice in choices]) @register.simple_tag -def token_decimal(length): - return generate_token(string.digits, length) +def token_decimal(length, *seeds): + return generate_token(string.digits, length, *seeds) @register.simple_tag -def token_alphanum(length): - return generate_token('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', length) +def token_alphanum(length, *seeds): + return generate_token('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', length, *seeds) @register.filter -- 2.20.1