entropy computation in IS_STRONG, thanks Jonathan
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 2.0.9 (2012-09-19 23:04:04) stable
|
Version 2.0.9 (2012-09-20 09:14:45) stable
|
||||||
|
|||||||
+2
-2
@@ -155,9 +155,9 @@ def read_possible_plural_rules():
|
|||||||
create list of all possible plural rules files
|
create list of all possible plural rules files
|
||||||
result is cached in PLURAL_RULES dictionary to increase speed
|
result is cached in PLURAL_RULES dictionary to increase speed
|
||||||
"""
|
"""
|
||||||
|
plurals = {}
|
||||||
try:
|
try:
|
||||||
import gluon.contrib.plural_rules as package
|
import contrib.plural_rules as package
|
||||||
plurals = {}
|
|
||||||
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
|
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
|
||||||
if len(modname)==2:
|
if len(modname)==2:
|
||||||
module = __import__(package.__name__+'.'+modname,
|
module = __import__(package.__name__+'.'+modname,
|
||||||
|
|||||||
+87
-10
@@ -2725,6 +2725,43 @@ class CRYPT(object):
|
|||||||
return ('', translate(self.error_message))
|
return ('', translate(self.error_message))
|
||||||
return (LazyCrypt(self,value),None)
|
return (LazyCrypt(self,value),None)
|
||||||
|
|
||||||
|
# entropy calculator for IS_STRONG
|
||||||
|
#
|
||||||
|
lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz'))
|
||||||
|
upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
|
||||||
|
numberset = frozenset(unicode('0123456789'))
|
||||||
|
sym1set = frozenset(unicode('!@#$%^&*()'))
|
||||||
|
sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/'))
|
||||||
|
otherset = frozenset(unicode('0123456789abcdefghijklmnopqrstuvwxyz')) # anything else
|
||||||
|
|
||||||
|
def calc_entropy(string):
|
||||||
|
" calculate a simple entropy for a given string "
|
||||||
|
import math
|
||||||
|
alphabet = 0 # alphabet size
|
||||||
|
other = set()
|
||||||
|
seen = set()
|
||||||
|
lastset = None
|
||||||
|
if isinstance(string, str):
|
||||||
|
string = unicode(string, encoding='utf8')
|
||||||
|
for c in string:
|
||||||
|
# classify this character
|
||||||
|
inset = otherset
|
||||||
|
for cset in (lowerset, upperset, numberset, sym1set, sym2set):
|
||||||
|
if c in cset:
|
||||||
|
inset = cset
|
||||||
|
break
|
||||||
|
# calculate effect of character on alphabet size
|
||||||
|
if inset not in seen:
|
||||||
|
seen.add(inset)
|
||||||
|
alphabet += len(inset) # credit for a new character set
|
||||||
|
elif c not in other:
|
||||||
|
alphabet += 1 # credit for unique characters
|
||||||
|
other.add(c)
|
||||||
|
if inset is not lastset:
|
||||||
|
alphabet += 1 # credit for set transitions
|
||||||
|
lastset = cset
|
||||||
|
entropy = len(string) * math.log(alphabet) / 0.6931471805599453 # math.log(2)
|
||||||
|
return round(entropy, 2)
|
||||||
|
|
||||||
class IS_STRONG(object):
|
class IS_STRONG(object):
|
||||||
"""
|
"""
|
||||||
@@ -2734,23 +2771,61 @@ class IS_STRONG(object):
|
|||||||
requires=IS_STRONG(min=10, special=2, upper=2))
|
requires=IS_STRONG(min=10, special=2, upper=2))
|
||||||
|
|
||||||
enforces complexity requirements on a field
|
enforces complexity requirements on a field
|
||||||
|
|
||||||
|
>>> IS_STRONG(es=True)('Abcd1234')
|
||||||
|
('Abcd1234', 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|')
|
||||||
|
>>> IS_STRONG(es=True)('Abcd1234!')
|
||||||
|
('Abcd1234!', None)
|
||||||
|
>>> IS_STRONG(es=True, entropy=1)('a')
|
||||||
|
('a', None)
|
||||||
|
>>> IS_STRONG(es=True, entropy=1, min=2)('a')
|
||||||
|
('a', 'Minimum length is 2')
|
||||||
|
>>> IS_STRONG(es=True, entropy=100)('abc123')
|
||||||
|
('abc123', 'Entropy (32.35) less than required (100)')
|
||||||
|
>>> IS_STRONG(es=True, entropy=100)('and')
|
||||||
|
('and', 'Entropy (14.57) less than required (100)')
|
||||||
|
>>> IS_STRONG(es=True, entropy=100)('aaa')
|
||||||
|
('aaa', 'Entropy (14.42) less than required (100)')
|
||||||
|
>>> IS_STRONG(es=True, entropy=100)('a1d')
|
||||||
|
('a1d', 'Entropy (15.97) less than required (100)')
|
||||||
|
>>> IS_STRONG(es=True, entropy=100)('añd')
|
||||||
|
('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
|
def __init__(self, min=None, max=None, upper=None, lower=None, number=None,
|
||||||
special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
entropy=None,
|
||||||
invalid=' "', error_message=None):
|
special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||||
self.min = min
|
invalid=' "', error_message=None, es=False):
|
||||||
self.max = max
|
self.entropy = entropy
|
||||||
self.upper = upper
|
if entropy is None:
|
||||||
self.lower = lower
|
# enforce default requirements
|
||||||
self.number = number
|
self.min = 8 if min is None else min
|
||||||
self.special = special
|
self.max = max # was 20, but that doesn't make sense
|
||||||
|
self.upper = 1 if upper is None else upper
|
||||||
|
self.lower = 1 if lower is None else lower
|
||||||
|
self.number = 1 if number is None else number
|
||||||
|
self.special = 1 if special is None else special
|
||||||
|
else:
|
||||||
|
# by default, an entropy spec is exclusive
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.upper = upper
|
||||||
|
self.lower = lower
|
||||||
|
self.number = number
|
||||||
|
self.special = special
|
||||||
self.specials = specials
|
self.specials = specials
|
||||||
self.invalid = invalid
|
self.invalid = invalid
|
||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
|
self.estring = es # return error message as string (for doctest)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
failures = []
|
failures = []
|
||||||
|
if self.entropy is not None:
|
||||||
|
entropy = calc_entropy(value)
|
||||||
|
if entropy < self.entropy:
|
||||||
|
failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") \
|
||||||
|
% dict(have=entropy, need=self.entropy))
|
||||||
if type(self.min) == int and self.min > 0:
|
if type(self.min) == int and self.min > 0:
|
||||||
if not len(value) >= self.min:
|
if not len(value) >= self.min:
|
||||||
failures.append(translate("Minimum length is %s") % self.min)
|
failures.append(translate("Minimum length is %s") % self.min)
|
||||||
@@ -2761,7 +2836,7 @@ class IS_STRONG(object):
|
|||||||
all_special = [ch in value for ch in self.specials]
|
all_special = [ch in value for ch in self.specials]
|
||||||
if self.special > 0:
|
if self.special > 0:
|
||||||
if not all_special.count(True) >= self.special:
|
if not all_special.count(True) >= self.special:
|
||||||
failures.append(translate("Must include at least %s of the following : %s") \
|
failures.append(translate("Must include at least %s of the following: %s") \
|
||||||
% (self.special, self.specials))
|
% (self.special, self.specials))
|
||||||
if self.invalid:
|
if self.invalid:
|
||||||
all_invalid = [ch in value for ch in self.invalid]
|
all_invalid = [ch in value for ch in self.invalid]
|
||||||
@@ -2801,6 +2876,8 @@ class IS_STRONG(object):
|
|||||||
if len(failures) == 0:
|
if len(failures) == 0:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
if not self.error_message:
|
if not self.error_message:
|
||||||
|
if self.estring:
|
||||||
|
return (value, '|'.join(failures))
|
||||||
from html import XML
|
from html import XML
|
||||||
return (value, XML('<br />'.join(failures)))
|
return (value, XML('<br />'.join(failures)))
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user