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.
This commit is contained in:
Benjamin Dauvergne 2019-03-11 14:41:08 +01:00
parent 51df06ed9d
commit bbeb4033ba
2 changed files with 56 additions and 0 deletions

View File

@ -2,6 +2,7 @@
import datetime
import pytest
import string
from quixote import cleanup
from qommon.template import Template, TemplateError
@ -432,3 +433,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'

View File

@ -20,6 +20,8 @@ from decimal import InvalidOperation as DecimalInvalidOperation
from decimal import DivisionByZero as DecimalDivisionByZero
import hashlib
import math
import string
import random
from django import template
from django.template import defaultfilters
@ -271,3 +273,30 @@ def abs_(value):
def version_hash():
from wcs.qommon.admin.menu import get_vc_version
return hashlib.md5(str(get_vc_version())).hexdigest()
def generate_token(alphabet, length):
r = random.SystemRandom()
return ''.join([r.choice(alphabet) for i in range(length)])
@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()