version 2.13.1

This commit is contained in:
mdipierro
2015-12-17 21:19:08 -06:00
parent 1636528a0f
commit 22c89d8dcc
13 changed files with 98 additions and 88 deletions
+14 -4
View File
@@ -1,8 +1,18 @@
## master
- Change .flash and .hidden to .w2p_flash and .w2p_hidden
- copy the static folder from the new welcome app when you update web2py
## 2.13.1
## 2.12.1
- added fabfile.py
- fixed oauth2 renew token, thanks dokime7
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
- allow passing unicode to template render
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
- allow HttpOnly cookies
- french pluralizaiton rules, thanks Mathieu Clabaut
- fixed bug in redirect to cas service, thanks Fernando González
- allow deploying to pythonanywhere from the web2py admin that you're running locally, thanks Leonel
- better tests
- many more bug fixes
## 2.12.1-3
- security fix: Validate for open redirect everywhere, not just in login()
- allow to pack invidual apps and selected files as packed exe files
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.12.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.13.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+1 -1
View File
@@ -120,7 +120,7 @@ def app_cleanup(app, request):
r = False
# Remove cache files
path = apath('%s/cache/' % app, request)
path = apath('%s/cache/' % app, request)
if os.path.exists(path):
CacheOnDisk(folder=path).clear()
for f in os.listdir(path):
+2 -2
View File
@@ -58,7 +58,7 @@ def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%)
old_mem = psutil.virtual_memory().percent
# if we have data in storage and utilization exceeds 90%
while storage and old_mem > percentage:
while storage and old_mem > percentage:
# removed oldest entry
storage.popitem(last=False)
# garbage collect
@@ -378,7 +378,7 @@ class CacheOnDisk(CacheAbstract):
def safe_apply(self, key, function, default_value=None):
"""
"""
Safely apply a function to the value of a key in storage and set
the return value of the function to it.
+30 -30
View File
@@ -470,21 +470,21 @@ def compile_views(folder, skip_failed_views=False):
"""
path = pjoin(folder, 'views')
failed_views = []
failed_views = []
for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'):
try:
data = parse_template(fname, path)
except Exception, e:
if skip_failed_views:
failed_views.append(file)
else:
raise Exception("%s in %s" % (e, file))
else:
filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
if skip_failed_views:
failed_views.append(file)
else:
raise Exception("%s in %s" % (e, file))
else:
filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
return failed_views if failed_views else None
@@ -675,25 +675,25 @@ def run_view_in(environment):
restricted(ccode, environment, 'file stream')
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)):
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
# for backward compatibility
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
compiled = pjoin(path, f)
if os.path.exists(compiled):
code = read_pyc(compiled)
restricted(code, environment, layer=compiled)
if os.path.exists(path): # compiled views
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)):
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
# for backward compatibility
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
compiled = pjoin(path, f)
if os.path.exists(compiled):
code = read_pyc(compiled)
restricted(code, environment, layer=compiled)
return
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
+2 -2
View File
@@ -41,7 +41,7 @@ class CustomImportException(ImportError):
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
"""
web2py's custom importer. It behaves like the standard Python importer but
web2py's custom importer. It behaves like the standard Python importer but
it tries to transform import statements as something like
"import applications.app_name.modules.x".
If the import fails, it falls back on naive_importer
@@ -80,7 +80,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
if not fromlist:
# import like "import x" or "import x.y"
result = None
for itemname in name.split("."):
for itemname in name.split("."):
new_mod = base_importer(
modules_prefix, globals, locals, [itemname], level)
try:
+1 -1
View File
@@ -900,7 +900,7 @@ def render(content="hello world",
if isinstance(content, unicode):
content = content.encode('utf8')
# save current response class
if context and 'response' in context:
old_response_body = context['response'].body
+13 -13
View File
@@ -16,19 +16,19 @@ from gluon.globals import Request, Response, Session
from gluon import URL
def setup_clean_session():
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
return current
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
return current
class testResponse(unittest.TestCase):
+1 -1
View File
@@ -309,7 +309,7 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
#bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
def testTAG(self):
+1 -1
View File
@@ -146,7 +146,7 @@ class TestList(unittest.TestCase):
'something')
# except if default is especified
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
def test_listgetitem(self):
'''Mantains list behaviour.'''
a = List((1, 2, 3))
+11 -11
View File
@@ -24,42 +24,42 @@ class TestUtils(unittest.TestCase):
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
def test_compare(self):
""" Tests the compare funciton """
a, b = 'test123', 'test123'
compare_result_true = compare(a, b)
self.assertTrue(compare_result_true)
a, b = 'test123', 'test456'
compare_result_false = compare(a, b)
self.assertFalse(compare_result_false)
def test_simple_hash(self):
""" Tests the simple_hash function """
# no key, no salt, md5
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, sha1
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
# no key, no salt, sha224
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
# no key, no salt, sha256
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
# no key, no salt, sha384
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
self.assertEqual(data_sha384,
self.assertEqual(data_sha384,
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
# no key, no salt, sha512
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
+20 -20
View File
@@ -1426,7 +1426,7 @@ class Auth(object):
csrf_prevention=True, propagate_extension=None,
url_index=None):
## next two lines for backward compatibility
## next two lines for backward compatibility
if not db and environment and isinstance(environment, DAL):
db = environment
self.db = db
@@ -1544,7 +1544,7 @@ class Auth(object):
self.define_signature()
else:
self.signature = None
def get_vars_next(self):
next = current.request.vars._next
if isinstance(next, (list, tuple)):
@@ -1554,7 +1554,7 @@ class Auth(object):
# _next variable in the request.
items = next.split('/')
if '//' in next and items[2] != current.request.env.http_host:
next = None
next = None
return next
def _get_user_id(self):
@@ -1611,7 +1611,7 @@ class Auth(object):
'retrieve_username', 'retrieve_password',
'reset_password', 'request_reset_password',
'change_password', 'profile', 'groups',
'impersonate', 'not_authorized', 'confirm_registration',
'impersonate', 'not_authorized', 'confirm_registration',
'bulk_register','manage_tokens'):
if len(request.args) >= 2 and args[0] == 'impersonate':
return getattr(self, args[0])(request.args[1])
@@ -2373,7 +2373,7 @@ class Auth(object):
and a raw password.
"""
settings = self._get_login_settings()
# users can register_bare even if no password is provided,
# users can register_bare even if no password is provided,
# in this case they will have to reset their password to login
if fields.get(settings.passfield):
fields[settings.passfield] = \
@@ -2381,7 +2381,7 @@ class Auth(object):
if not fields.get(settings.userfield):
raise ValueError('register_bare: ' +
'userfield not provided or invalid')
user = self.get_or_create_user(fields, login=False, get=False,
user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields)
if not user:
# get or create did not create a user (it ignores duplicate records)
@@ -2732,7 +2732,7 @@ class Auth(object):
# username and password at the first challenge).
# Check if this user is signed up for two-factor authentication
# If auth.settings.auth_two_factor_enabled it will enable two factor
# for all the app. Another way to anble two factor is that the user
# for all the app. Another way to anble two factor is that the user
# must be part of a group that is called auth.settings.two_factor_authentication_group
if user and self.settings.auth_two_factor_enabled == True:
session.auth_two_factor_enabled = True
@@ -2763,7 +2763,7 @@ class Auth(object):
session.auth_two_factor_tries_left = self.settings.auth_two_factor_tries_left
# Set the way we generate the code or we send the code. For example using SMS...
two_factor_methods = self.settings.two_factor_methods
if two_factor_methods == []:
# TODO: Add some error checking to handle cases where email cannot be sent
self.settings.mailer.send(
@@ -2780,19 +2780,19 @@ class Auth(object):
pass
else:
break
if form.accepts(request, session if self.csrf_prevention else None,
formname='login', dbio=False,
onvalidation=onvalidation,
hideerror=settings.hideerror):
accepted_form = True
accepted_form = True
'''
The lists is executed after form validation for each of the corresponding action.
For example, in your model:
In your models copy and paste:
#Before define tables, we add some extra field to auth_user
@@ -2802,7 +2802,7 @@ class Auth(object):
OFFSET = 60 #Be sure is the same in your OTP Client
#Set session.auth_two_factor to None. Because the code is generated by external app.
#Set session.auth_two_factor to None. Because the code is generated by external app.
# This will avoid to use the default setting and send a code by email.
def _set_two_factor(user, auth_two_factor):
return None
@@ -2823,10 +2823,10 @@ class Auth(object):
auth.messages.two_factor_comment = "Verify your OTP Client for the code."
auth.settings.two_factor_methods = [lambda user, auth_two_factor: _set_two_factor(user, auth_two_factor)]
auth.settings.two_factor_onvalidation = [lambda user, otp: verify_otp(user, otp)]
'''
if self.settings.two_factor_onvalidation != []:
for two_factor_onvalidation in self.settings.two_factor_onvalidation:
try:
session.auth_two_factor = two_factor_onvalidation(session.auth_two_factor_user, form.vars['authentication_code'])
@@ -2834,7 +2834,7 @@ class Auth(object):
pass
else:
break
if form.vars['authentication_code'] == str(session.auth_two_factor):
# Handle the case when the two-factor form has been successfully validated
# and the user was previously stored (the current user should be None because
@@ -2994,7 +2994,7 @@ class Auth(object):
except:
pass
if self.settings.register_verify_password:
if self.settings.register_verify_password:
if self.settings.register_fields is None:
self.settings.register_fields = [f.name for f in table_user if f.writable]
k = self.settings.register_fields.index("password")
@@ -3005,7 +3005,7 @@ class Auth(object):
error_message=self.messages.mismatched_password),
label=current.T("Confirm Password"))]
else:
extra_fields = []
extra_fields = []
form = SQLFORM(table_user,
fields=self.settings.register_fields,
hidden=dict(_next=next),
@@ -3382,7 +3382,7 @@ class Auth(object):
if form.process().accepted:
emails = re.compile('[^\s\'"@<>,;:]+\@[^\s\'"@<>,;:]+').findall(form.vars.emails)
# send the invitations
# send the invitations
emails_sent = []
emails_fail = []
emails_exist = []
@@ -3403,7 +3403,7 @@ class Auth(object):
def manage_tokens(self):
if not self.user:
redirect(self.settings.login_url)
redirect(self.settings.login_url)
table_token =self.table_token()
table_token.user_id.writable = False
table_token.user_id.default = self.user.id
+1 -1
View File
@@ -3367,7 +3367,7 @@ class IS_IPV4(Validator):
(number == self.localhost)):
ok = False
if not (self.is_private is None or self.is_private ==
(sum([private_number[0] <= number <= private_number[1]
(sum([private_number[0] <= number <= private_number[1]
for private_number in self.private]) > 0)):
ok = False
if not (self.is_automatic is None or self.is_automatic ==