From 8d9bc1cb53123ddb4001c7a51a512884eb2d80de Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 19 Sep 2012 00:00:18 -0500 Subject: [PATCH 01/12] mail.send(...,sender='Mr X <%(sender)s>') --- VERSION | 2 +- gluon/tools.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 7ba0fb63..9cd82f17 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-18 13:39:12) stable +Version 2.0.9 (2012-09-19 00:00:12) stable diff --git a/gluon/tools.py b/gluon/tools.py index 6e449085..3a0bf7a5 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -264,6 +264,7 @@ class Mail(object): cc=None, bcc=None, reply_to=None, + sender='%(sender)s', encoding='utf-8', raw=False, headers={} @@ -606,7 +607,7 @@ class Mail(object): # no cryptography process as usual payload=payload_in - payload['From'] = encoded_or_raw(self.settings.sender.decode(encoding)) + payload['From'] = encoded_or_raw(sender.decode(encoding)) origTo = to[:] if to: payload['To'] = encoded_or_raw(', '.join(to).decode(encoding)) @@ -623,10 +624,11 @@ class Mail(object): for k,v in headers.iteritems(): payload[k] = encoded_or_raw(v.decode(encoding)) result = {} - try: + sender = sender % dict(sender=self.settings.sender) + try: if self.settings.server == 'logging': logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \ - ('-'*40,self.settings.sender, + ('-'*40,sender, ', '.join(to),subject, text or html,'-'*40)) elif self.settings.server == 'gae': From f371e72e073aa974abd8abd470be02d4a7dd6506 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 19 Sep 2012 00:03:06 -0500 Subject: [PATCH 02/12] mail.send(...,sender='Mr X <%(sender)s>') --- VERSION | 2 +- gluon/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 9cd82f17..2c74dec2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-19 00:00:12) stable +Version 2.0.9 (2012-09-19 00:03:03) stable diff --git a/gluon/tools.py b/gluon/tools.py index 3a0bf7a5..6c1d21ad 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -607,6 +607,7 @@ class Mail(object): # no cryptography process as usual payload=payload_in + sender = sender % dict(sender=self.settings.sender) payload['From'] = encoded_or_raw(sender.decode(encoding)) origTo = to[:] if to: @@ -624,7 +625,6 @@ class Mail(object): for k,v in headers.iteritems(): payload[k] = encoded_or_raw(v.decode(encoding)) result = {} - sender = sender % dict(sender=self.settings.sender) try: if self.settings.server == 'logging': logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \ From 586d85cf088f6ee2c9a13d1c63455757996fd657 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 19 Sep 2012 23:04:10 -0500 Subject: [PATCH 03/12] fixed a problem with reset_password redirect loop --- VERSION | 2 +- gluon/tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 2c74dec2..4d0f352a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-19 00:03:03) stable +Version 2.0.9 (2012-09-19 23:04:04) stable diff --git a/gluon/tools.py b/gluon/tools.py index 6c1d21ad..6130a226 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1197,7 +1197,7 @@ class Auth(object): 'reset_password','request_reset_password', 'change_password','profile','groups', 'impersonate','not_authorized'): - if len(request.args) >= 2: + if len(request.args) >= 2 and args[0]=='impersonate': return getattr(self,args[0])(request.args[1]) else: return getattr(self,args[0])() @@ -2445,7 +2445,7 @@ class Auth(object): session = current.session if next is DEFAULT: - next = self.settings.reset_password_next + next = self.next or self.settings.reset_password_next try: key = request.vars.key or getarg(-1) t0 = int(key.split('-')[0]) From 7a0cf446ba8564fb5f8e7fdec0784f213096286e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 20 Sep 2012 09:14:51 -0500 Subject: [PATCH 04/12] entropy computation in IS_STRONG, thanks Jonathan --- VERSION | 2 +- gluon/languages.py | 4 +- gluon/validators.py | 97 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index 4d0f352a..b87091cc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-19 23:04:04) stable +Version 2.0.9 (2012-09-20 09:14:45) stable diff --git a/gluon/languages.py b/gluon/languages.py index d09e55bb..0b669d6b 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -155,9 +155,9 @@ def read_possible_plural_rules(): create list of all possible plural rules files result is cached in PLURAL_RULES dictionary to increase speed """ + plurals = {} try: - import gluon.contrib.plural_rules as package - plurals = {} + import contrib.plural_rules as package for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): if len(modname)==2: module = __import__(package.__name__+'.'+modname, diff --git a/gluon/validators.py b/gluon/validators.py index 90c5b81b..457720a1 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -2725,6 +2725,43 @@ class CRYPT(object): return ('', translate(self.error_message)) 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): """ @@ -2734,23 +2771,61 @@ class IS_STRONG(object): requires=IS_STRONG(min=10, special=2, upper=2)) 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, - special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|', - invalid=' "', error_message=None): - self.min = min - self.max = max - self.upper = upper - self.lower = lower - self.number = number - self.special = special + def __init__(self, min=None, max=None, upper=None, lower=None, number=None, + entropy=None, + special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|', + invalid=' "', error_message=None, es=False): + self.entropy = entropy + if entropy is None: + # enforce default requirements + self.min = 8 if min is None else min + 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.invalid = invalid self.error_message = error_message + self.estring = es # return error message as string (for doctest) def __call__(self, value): 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 not len(value) >= 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] if self.special > 0: 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)) if self.invalid: all_invalid = [ch in value for ch in self.invalid] @@ -2801,6 +2876,8 @@ class IS_STRONG(object): if len(failures) == 0: return (value, None) if not self.error_message: + if self.estring: + return (value, '|'.join(failures)) from html import XML return (value, XML('
'.join(failures))) else: From 2385ed22b1eabdfd9b313c54ef29eddcbc7ad9b2 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 20 Sep 2012 09:16:04 -0500 Subject: [PATCH 05/12] new web2py_bootstrap_nojs.css, thanks Paolo --- VERSION | 2 +- .../static/css/web2py_bootstrap_nojs.css | 90 ++++++++++++++++++- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index b87091cc..d1bc5824 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-20 09:14:45) stable +Version 2.0.9 (2012-09-20 09:15:59) stable diff --git a/applications/welcome/static/css/web2py_bootstrap_nojs.css b/applications/welcome/static/css/web2py_bootstrap_nojs.css index 2b75915e..0ec7312f 100644 --- a/applications/welcome/static/css/web2py_bootstrap_nojs.css +++ b/applications/welcome/static/css/web2py_bootstrap_nojs.css @@ -1,3 +1,23 @@ +/*============================================================= + BOOTSTRAP DROPDOWN MENU +==============================================================*/ + +.dropdown-menu ul{ + left:100%; + position:absolute; + top:0; + visibility:hidden; + margin-top:-1px; +} +.dropdown-menu li:hover ul{visibility:visible;} +.navbar .dropdown-menu ul:before{ + border-bottom:7px solid transparent; + border-left:none; + border-right:7px solid rgba(0, 0, 0, 0.2); + border-top:7px solid transparent; + left:-7px; + top:5px; +} .nav > li.dropdown > a:after { border-left: 4px solid transparent; border-right: 4px solid transparent; @@ -15,8 +35,7 @@ border-bottom-color: #FFFFFF; border-top-color: #FFFFFF; } - - +.dropdown-menu span{display:inline-block;} ul.dropdown-menu li.dropdown > a:after { border-left: 4px solid #000; border-right: 4px solid transparent; @@ -35,4 +54,69 @@ ul.dropdown-menu li.dropdown > a:after { ul.nav li.dropdown:hover ul.dropdown-menu { display: block; -} \ No newline at end of file +} + +.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */ + +/*============================================================= + BOOTSTRAP SUBMIT BUTTON +==============================================================*/ + +input[type='submit']:not(.btn) { +display: inline-block; +padding: 4px 14px; +margin-bottom: 0; +font-size: 14px; +line-height: 20px; +color: #333; +text-align: center; +text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); +vertical-align: middle; +cursor: pointer; +background-color: whiteSmoke; +background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6)); +background-image: -webkit-linear-gradient(top,white,#E6E6E6); +background-image: -o-linear-gradient(top,white,#E6E6E6); +background-image: linear-gradient(to bottom,white,#E6E6E6); +background-image: -moz-linear-gradient(top,white,#E6E6E6); +background-repeat: repeat-x; +border: 1px solid #BBB; +border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); +border-bottom-color: #A2A2A2; +-webkit-border-radius: 4px; +-moz-border-radius: 4px; +border-radius: 4px; +filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0); +filter: progid:dximagetransform.microsoft.gradient(enabled=false); +-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); +-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05); +box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05); +} + +input[type='submit']:not(.btn):hover { +color: #333; +text-decoration: none; +background-color: #E6E6E6; +background-position: 0 -15px; +-webkit-transition: background-position .1s linear; +-moz-transition: background-position .1s linear; +-o-transition: background-position .1s linear; +transition: background-position .1s linear; +} + +input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active { +background-color: #E6E6E6; +background-color: #D9D9D9 9; +background-image: none; +outline: 0; +-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); +-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); +box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); +} + +/*============================================================= + OTHER +==============================================================*/ + +.ie-lte8 .navbar-fixed-top {position:static;} + From 8b752562814510f26249ee224a75d9af0586a760 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 20 Sep 2012 09:19:21 -0500 Subject: [PATCH 06/12] better layout yet, thanks Paolo --- VERSION | 2 +- applications/welcome/models/menu.py | 4 ++-- applications/welcome/static/css/web2py_bootstrap.css | 12 +++++++++++- applications/welcome/views/layout.html | 8 ++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index d1bc5824..3fb27561 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.9 (2012-09-20 09:15:59) stable +Version 2.0.9 (2012-09-20 09:19:17) stable diff --git a/applications/welcome/models/menu.py b/applications/welcome/models/menu.py index 784f4a4a..dcf1c275 100644 --- a/applications/welcome/models/menu.py +++ b/applications/welcome/models/menu.py @@ -33,9 +33,9 @@ def _(): # shortcuts app = request.application ctr = request.controller - # useful links to internal and external resources + # useful links to internal and external resources response.menu+=[ - (SPAN('web2py',_style='color:yellow'),False, 'http://web2py.com', [ + (SPAN('web2py',_class='highlighted'),False, 'http://web2py.com', [ (T('My Sites'),False,URL('admin','default','site')), (T('This App'),False,URL('admin','default','design/%s' % app), [ (T('Controller'),False, diff --git a/applications/welcome/static/css/web2py_bootstrap.css b/applications/welcome/static/css/web2py_bootstrap.css index 0c2cf74c..2f419f8c 100644 --- a/applications/welcome/static/css/web2py_bootstrap.css +++ b/applications/welcome/static/css/web2py_bootstrap.css @@ -70,7 +70,15 @@ div.controls .error{ //display:inline; /* uncommenting this, the animation effect is lost */ } div.controls .inline-help{color:#3A87AD;} -div.controls .error_wrapper+.inline-help{margin-left:-99999px} +div.controls .error_wrapper+.inline-help{margin-left:-99999px;} +/* beautify brand */ +.navbar-inverse .brand{color:#c6cecc;} +.navbar-inverse .brand b{display:inline-block;margin-top:-1px;} +.navbar-inverse .brand b>span{font-size:22px;color:#425f5a;} +.navbar-inverse .brand:hover b>span{color:#638c86;} +/* beautify web2py link in navbar */ +span.highlighted{color:#d8d800;} +.open span.highlighted{color:#ffff00;} /*============================================================= OVERRIDING WEB2PY.CSS RULES @@ -185,6 +193,8 @@ td.w2p_fw ul{margin-left:0px;} margin:3px 0 0 2px; } .web2py_grid form table{width:auto;} +/* auth_user_remember checkbox extrapadding in IE fix */ +.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;} /*============================================================= MEDIA QUERIES diff --git a/applications/welcome/views/layout.html b/applications/welcome/views/layout.html index d0a2fd2f..19225730 100644 --- a/applications/welcome/views/layout.html +++ b/applications/welcome/views/layout.html @@ -76,7 +76,7 @@ - web2py™  + web2py™