From d2aeb2c81283904527383698235f7f0032596bf7 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 | 31 +++++++++++++++++++++++++++++++ 2 files changed, 59 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..95a4e763 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,31 @@ def floor(value): @register.filter(name='abs') 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)] + return ''.join([alphabet[choice] for choice in choices]) + + +@register.simple_tag +def token_decimal(length=6): + # 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) + + +@register.simple_tag +def token_alphanum(length=4): + # 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) + + +@register.filter +def token_check(token1, token2): + return token1.strip().upper() == token2.strip().upper() -- 2.20.1