From d04ca1848cedc2902b1dd85d2be4ef7b9fa520da Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 11 Mar 2019 14:41:08 +0100 Subject: [PATCH 1/2] templatetags: add tags for token generation/validation (#31268) - add {% token_decimal n %} -> n digits random token - add {% token_alphanum n %} -> n digits/uppercase-letters (without 0,1,I and O) random token - token1|token_check:token2 -> verify token1 is equal to token2 insensitive to case and prefix/suffix spaces. --- tests/test_templates.py | 28 ++++++++++++++++++++++++++++ wcs/qommon/templatetags/qommon.py | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/tests/test_templates.py b/tests/test_templates.py index cae7e9fe..b2735797 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- +import random import datetime import pytest +import string from quixote import cleanup from qommon.template import Template, TemplateError @@ -432,3 +434,29 @@ def test_abs_templatetag(): assert tmpl.render({'value': 'not a number'}) == '0' assert tmpl.render({'value': ''}) == '0' assert tmpl.render({'value': None}) == '0' + + +def test_token_decimal(): + tokens = [Template('{% token_decimal 4 %}').render() for i in range(100)] + assert all(len(token) == 4 for token in tokens) + assert all(token.isdigit() for token in tokens) + # check randomness, i.e. duplicates are rare + assert len(set(tokens)) > 70 + t = Template('{% if token1|token_check:token2 %}ok{% endif %}') + assert t.render({'token1': tokens[0] + ' ', 'token2': tokens[0].lower()}) == 'ok' + + +def test_token_alphanum(): + tokens = [Template('{% token_alphanum 4 %}').render() for i in range(100)] + assert all(len(token) == 4 for token in tokens) + assert all(token.upper() == token for token in tokens) + assert all(token.isalnum() for token in tokens) + # check randomness, i.e. duplicates are rare + assert len(set(tokens)) > 90 + # check there are letters and digits + assert any(set(token) & set(string.ascii_uppercase) for token in tokens) + assert any(set(token) & set(string.digits) for token in tokens) + # no look-alike characters + 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' diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py index 81606a74..393823ea 100644 --- a/wcs/qommon/templatetags/qommon.py +++ b/wcs/qommon/templatetags/qommon.py @@ -19,6 +19,9 @@ from decimal import Decimal from decimal import InvalidOperation as DecimalInvalidOperation from decimal import DivisionByZero as DecimalDivisionByZero import math +import string +import hashlib +import random from django import template from django.template import defaultfilters @@ -265,3 +268,26 @@ def floor(value): @register.filter(name='abs') def abs_(value): return decimal(abs(parse_decimal(value))) + + +def generate_token(alphabet, length): + # 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)] + return ''.join([alphabet[choice] for choice in choices]) + + +@register.simple_tag +def token_decimal(length): + return generate_token(string.digits, length) + + +@register.simple_tag +def token_alphanum(length): + return generate_token('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', length) + + +@register.filter +def token_check(token1, token2): + return token1.strip().upper() == token2.strip().upper() -- 2.20.1