Compare commits

...
32 Commits
Author SHA1 Message Date
mdipierro 7e96ecafd7 R-2.15.3 2017-08-07 07:41:11 -05:00
mdipierro 86ea728f4f R-2.15.3 2017-08-07 07:32:58 -05:00
mdipierro a29947f298 websocket_messaging in Python 3 #1696, thanks hirolau 2017-08-07 07:27:28 -05:00
mdipierro dc29f33365 addressed __ssl.pyd issue #1716, thanks Jhleite 2017-08-07 07:18:42 -05:00
mdipierroandGitHub 834460f0cc Merge pull request #1723 from leonelcamara/patch-6
Small unicode compatibility py3 fixes
2017-08-07 07:09:30 -05:00
mdipierroandGitHub 5353e17c66 Merge pull request #1720 from leonelcamara/patch-4
Fix BEAUTIFY trying to display uploaded file contents
2017-08-07 07:08:58 -05:00
mdipierroandGitHub d2022ca500 Merge pull request #1719 from leonelcamara/patch-3
Fix response.download with nonasccii filenames
2017-08-07 07:08:33 -05:00
mdipierroandGitHub c560f607af Merge pull request #1710 from willimoa/issue/1704
Fix Issue 1709
2017-08-07 07:08:10 -05:00
Leonel CâmaraandGitHub 3eea1f68f4 Make sure you return bytes when you str(body) 2017-08-07 00:20:29 +01:00
Leonel CâmaraandGitHub 10b6b93cb2 minor py3 compatibility change 2017-08-07 00:17:43 +01:00
Leonel CâmaraandGitHub 90e20a4f39 Fix BEAUTIFY trying to display uploaded file contents
Fixes #1717
2017-08-06 17:01:27 +01:00
Leonel CâmaraandGitHub a744835f21 Fix response.download with nonasccii filenames
Fixes #1718
2017-08-05 13:27:44 +01:00
mdipierro ebc614bf91 fixed has_ssl, thanks leonel 2017-08-04 10:21:43 -05:00
mdipierro b7270df9c3 fixed issue topic/web2py/UnN6AyOh2Lg thanks Paul 2017-08-04 09:58:05 -05:00
Andrew Willimott 4a47bb8e83 d3_graph_model fixed, no longer hardcoded for a “db” database 2017-08-02 21:47:26 +12:00
mdipierro 213c4ee7d1 fixed use of whitespaces 2017-08-01 10:26:33 -05:00
mdipierroandGitHub 7088b74d42 Merge pull request #1705 from josedesoto/enhancement/1557
Enhancement/1557
2017-08-01 09:46:55 -05:00
mdipierroandGitHub 752b5a8cdb Merge pull request #1704 from josedesoto/enhancement/response_json_sort_paramenter
Allow response.json to be sortable or not
2017-08-01 09:46:11 -05:00
mdipierroandGitHub 56e6d682d6 Merge pull request #1702 from leonelcamara/i1699
Fix autocomplete called after user_signature chk
2017-08-01 09:46:00 -05:00
mdipierroandGitHub c1881fa205 Merge pull request #1701 from jkotyz/issue/1700
Fixes 1700 - utc and local time inconsistency in AuthJWT
2017-08-01 09:45:46 -05:00
mdipierroandGitHub cc2cae5de4 Merge pull request #1692 from timnyborg/master
Fix #1691: Passing tables to SQLFORM.factory() fails
2017-08-01 09:45:21 -05:00
mdipierroandGitHub cedd74c1ad Merge pull request #1690 from josedesoto/issue/1689
del_membership prevent update self user if user_id has a value
2017-08-01 09:44:41 -05:00
Jose de Soto d5167f2ed6 change_password_url parameter for alternate login methods 2017-07-31 19:00:24 +02:00
Jose de Soto 1014d3e86e new parameter to auto create or not users with alternate login methods 2017-07-31 18:33:15 +02:00
Jose de Soto f3bba29287 Allow response.json to be sortable or not 2017-07-31 17:46:46 +02:00
mdipierro cd2ec98a26 grid.dbset 2017-07-29 01:31:25 -05:00
mdipierro 0e613f2d7f allow DAL(..., adapter_args=dict(migrator=InDBMigrator)) 2017-07-28 16:05:35 -05:00
Leonel CâmaraandGitHub 561828fa60 Fix autocomplete called after user_signature chk
Fixes #1699 which was caused by the autocomplete widget only being called after a user_signature check and the signature not being there.  

The default values for user_signature and hash_vars make this work with the grid when it has the user_signature=True option without any problem when that isn't the case. Users can disable it if they want or change it according to what they're verifying in their controllers.   
  
The autocomplete widget will still fail with the default kwargs in a situation where it is called in a controller function that first verifies the URL signature with hash_vars=True.  
  
For users having that problem, there's no way around this without inserting a security flaw, so they must sadly change their application code to give the autocomplete widget the needed user_signature and hash_vars argument.
2017-07-28 02:20:54 +01:00
Jan Kotyz 19efbfecfa Fixes 1700 2017-07-27 11:27:41 +02:00
Tim NyborgandGitHub d43604c3ff List comprehension rather than loop
I've now learned this is quicker
2017-07-24 10:15:12 +01:00
Tim NyborgandGitHub ec21f72ce3 Fixes #1691
Clone fields, but leave tables untouched in factory()
2017-07-21 10:20:08 +01:00
Jose de Soto aa0313c59b del_membership prevent update self user if user_id has a value 2017-07-21 10:41:58 +02:00
22 changed files with 183 additions and 137 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
## 2.15.0b1 ## 2.15.1-3
- dropped support for python 2.6 - dropped support for python 2.6
- dropped web shell - dropped web shell
- experimental python 3 support - experimental python 3 support
+3 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.15.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.15.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
@@ -97,6 +97,8 @@ win:
cp -r applications/welcome ../web2py_win/web2py/applications cp -r applications/welcome ../web2py_win/web2py/applications
cp -r applications/examples ../web2py_win/web2py/applications cp -r applications/examples ../web2py_win/web2py/applications
cp applications/__init__.py ../web2py_win/web2py/applications cp applications/__init__.py ../web2py_win/web2py/applications
# per https://github.com/web2py/web2py/issues/1716
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy
cd ../web2py_win; zip -r web2py_win.zip web2py cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip . mv ../web2py_win/web2py_win.zip .
run: run:
+1 -1
View File
@@ -1 +1 @@
Version 2.15.2-stable+timestamp.2017.07.19.01.21.31 Version 2.15.3-stable+timestamp.2017.08.07.07.32.04
+24 -25
View File
@@ -657,37 +657,36 @@ def d3_graph_model():
Create a list of table dicts, called "nodes" Create a list of table dicts, called "nodes"
""" """
data = {}
nodes = [] nodes = []
links = [] links = []
subgraphs = dict() for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
for tablename in db.tables: if isinstance(f_type,str) and (
fields = [] f_type.startswith('reference') or
for field in db[tablename]: f_type.startswith('list:reference')):
f_type = field.type referenced_table = f_type.split()[1].split('.')[0]
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and ( links.append(dict(source=tablename, target = referenced_table))
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table)) nodes.append(dict(name=tablename, type="table", fields = fields))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
+24 -25
View File
@@ -657,37 +657,36 @@ def d3_graph_model():
Create a list of table dicts, called "nodes" Create a list of table dicts, called "nodes"
""" """
data = {}
nodes = [] nodes = []
links = [] links = []
subgraphs = dict() for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
for tablename in db.tables: if isinstance(f_type,str) and (
fields = [] f_type.startswith('reference') or
for field in db[tablename]: f_type.startswith('list:reference')):
f_type = field.type referenced_table = f_type.split()[1].split('.')[0]
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and ( links.append(dict(source=tablename, target = referenced_table))
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table)) nodes.append(dict(name=tablename, type="table", fields = fields))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
+24 -25
View File
@@ -657,37 +657,36 @@ def d3_graph_model():
Create a list of table dicts, called "nodes" Create a list of table dicts, called "nodes"
""" """
data = {}
nodes = [] nodes = []
links = [] links = []
subgraphs = dict() for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
for tablename in db.tables: if isinstance(f_type,str) and (
fields = [] f_type.startswith('reference') or
for field in db[tablename]: f_type.startswith('list:reference')):
f_type = field.type referenced_table = f_type.split()[1].split('.')[0]
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and ( links.append(dict(source=tablename, target = referenced_table))
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table)) nodes.append(dict(name=tablename, type="table", fields = fields))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
+1 -1
View File
@@ -557,7 +557,7 @@ class AuthAPI(object):
self.log_event(self.messages['del_membership_log'], self.log_event(self.messages['del_membership_log'],
dict(user_id=user_id, group_id=group_id)) dict(user_id=user_id, group_id=group_id))
ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete() ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
if group_id in self.user_groups: if group_id in self.user_groups and user_id == self.user_id:
del self.user_groups[group_id] del self.user_groups[group_id]
return ret return ret
+12 -1
View File
@@ -49,7 +49,8 @@ class CasAuth(object):
email=lambda v: v.get('email', None), email=lambda v: v.get('email', None),
user_id=lambda v: v['user']), user_id=lambda v: v['user']),
casversion=1, casversion=1,
casusername='cas:user' casusername='cas:user',
change_password_url=None
): ):
self.urlbase = urlbase self.urlbase = urlbase
self.cas_login_url = "%s/%s" % (self.urlbase, actions[0]) self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
@@ -64,6 +65,9 @@ class CasAuth(object):
#vars=current.request.vars, #vars=current.request.vars,
scheme=True) scheme=True)
# URL to let users change their password in the IDP system
self.cas_change_password_url = change_password_url
def login_url(self, next="/"): def login_url(self, next="/"):
current.session.token = self._CAS_login() current.session.token = self._CAS_login()
return next return next
@@ -74,6 +78,10 @@ class CasAuth(object):
self._CAS_logout() self._CAS_logout()
return next return next
def change_password_url(self, next="/"):
self._CAS_change_password()
return next
def get_user(self): def get_user(self):
user = current.session.token user = current.session.token
if user: if user:
@@ -135,3 +143,6 @@ class CasAuth(object):
redirects to the CAS logout page redirects to the CAS logout page
""" """
redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url)) redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
def _CAS_change_password(self):
redirect(self.cas_change_password_url)
+23 -1
View File
@@ -145,10 +145,16 @@ class Saml2Auth(object):
username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0], username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
email=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0], email=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0], user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
)): ), logout_url=None, change_password_url=None):
self.config_file = config_file self.config_file = config_file
self.maps = maps self.maps = maps
# URL for redirecting users to when they sign out
self.saml_logout_url = logout_url
# URL to let users change their password in the IDP system
self.saml_change_password_url = change_password_url
def login_url(self, next="/"): def login_url(self, next="/"):
d = saml2_handler(current.session, current.request) d = saml2_handler(current.session, current.request)
if 'url' in d: if 'url' in d:
@@ -170,6 +176,12 @@ class Saml2Auth(object):
def logout_url(self, next="/"): def logout_url(self, next="/"):
current.session.saml2_info = None current.session.saml2_info = None
current.session.auth = None
self._SAML_logout()
return next
def change_password_url(self, next="/"):
self._SAML_change_password()
return next return next
def get_user(self): def get_user(self):
@@ -180,3 +192,13 @@ class Saml2Auth(object):
d[key] = self.maps[key](user) d[key] = self.maps[key](user)
return d return d
return None return None
def _SAML_logout(self):
"""
exposed SAML.logout()
redirects to the SAML logout page
"""
redirect(self.saml_logout_url)
def _SAML_change_password(self):
redirect(self.saml_change_password_url)
+3 -25
View File
@@ -94,32 +94,10 @@ import optparse
import time import time
import sys import sys
import gluon.utils import gluon.utils
from gluon._compat import to_native, to_bytes, urlencode, urlopen
if (sys.version_info[0] == 2):
from urllib import urlencode, urlopen
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if isinstance(obj, unicode):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
else:
from urllib.request import urlopen
from urllib.parse import urlencode
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if isinstance(obj, str):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
listeners, names, tokens = {}, {}, {} listeners, names, tokens = {}, {}, {}
def websocket_send(url, message, hmac_key=None, group='default'): def websocket_send(url, message, hmac_key=None, group='default'):
sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or '' sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
params = urlencode( params = urlencode(
@@ -138,8 +116,8 @@ class PostHandler(tornado.web.RequestHandler):
if hmac_key and not 'signature' in self.request.arguments: if hmac_key and not 'signature' in self.request.arguments:
self.send_error(401) self.send_error(401)
if 'message' in self.request.arguments: if 'message' in self.request.arguments:
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0].decode(encoding='UTF-8')
group = self.request.arguments.get('group', ['default'])[0] group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
print('%s:MESSAGE to %s:%s' % (time.time(), group, message)) print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
+1
View File
@@ -14,6 +14,7 @@ from pydal import DAL as DAL
from pydal import Field from pydal import Field
from pydal.objects import Row, Rows, Table, Query, Set, Expression from pydal.objects import Row, Rows, Table, Query, Set, Expression
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
from pydal.migrator import Migrator, InDBMigrator
from gluon.serializers import custom_json, xml from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid from gluon.utils import web2py_uuid
from gluon import sqlhtml from gluon import sqlhtml
+6 -1
View File
@@ -14,7 +14,7 @@ Contains the classes for the global used variables:
""" """
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \ from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
unicodeT, long, hashlib_md5 unicodeT, long, hashlib_md5, urllib_quote
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
from gluon.contenttype import contenttype from gluon.contenttype import contenttype
@@ -641,6 +641,11 @@ class Response(Storage):
if download_filename is None: if download_filename is None:
download_filename = filename download_filename = filename
if attachment: if attachment:
# Browsers still don't have a simple uniform way to have non ascii
# characters in the filename so for now we are percent encoding it
if isinstance(download_filename, unicodeT):
download_filename = download_filename.encode('utf-8')
download_filename = urllib_quote(download_filename)
headers['Content-Disposition'] = \ headers['Content-Disposition'] = \
'attachment; filename="%s"' % download_filename.replace('"', '\"') 'attachment; filename="%s"' % download_filename.replace('"', '\"')
return self.stream(stream, chunk_size=chunk_size, request=request) return self.stream(stream, chunk_size=chunk_size, request=request)
+1 -1
View File
@@ -2431,7 +2431,7 @@ class BEAUTIFY(DIV):
if level == 0: if level == 0:
return return
for c in self.components: for c in self.components:
if hasattr(c, 'value') and not callable(c.value): if hasattr(c, 'value') and not callable(c.value) and not isinstance(c, cgi.FieldStorage):
if c.value: if c.value:
components.append(c.value) components.append(c.value)
if hasattr(c, 'xml') and callable(c.xml): if hasattr(c, 'xml') and callable(c.xml):
+8 -3
View File
@@ -11,7 +11,7 @@ HTTP statuses helpers
""" """
import re import re
from gluon._compat import iteritems, unicodeT from gluon._compat import iteritems, unicodeT, to_bytes
__all__ = ['HTTP', 'redirect'] __all__ = ['HTTP', 'redirect']
@@ -111,6 +111,8 @@ class HTTP(Exception):
if not body: if not body:
body = status body = status
if isinstance(body, (str, bytes, bytearray)): if isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT):
body = to_bytes(body) # This must be done before len
headers['Content-Length'] = len(body) headers['Content-Length'] = len(body)
rheaders = [] rheaders = []
for k, v in iteritems(headers): for k, v in iteritems(headers):
@@ -123,12 +125,15 @@ class HTTP(Exception):
return [''] return ['']
elif isinstance(body, (str, bytes, bytearray)): elif isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT): if isinstance(body, unicodeT):
body = body.encode('utf-8') body = to_bytes(body)
return [body] return [body]
elif hasattr(body, '__iter__'): elif hasattr(body, '__iter__'):
return body return body
else: else:
return [str(body)] body = str(body)
if isinstance(body, unicodeT):
body = to_bytes(body)
return [body]
@property @property
def message(self): def message(self):
+1 -1
View File
@@ -745,7 +745,7 @@ class HttpServer(object):
sock_list = [ip, port] sock_list = [ip, port]
if not ssl_certificate or not ssl_private_key: if not ssl_certificate or not ssl_private_key:
logger.info('SSL is off') logger.info('SSL is off')
elif not rocket.ssl: elif not rocket.has_ssl:
logger.warning('Python "ssl" module unavailable. SSL is OFF') logger.warning('Python "ssl" module unavailable. SSL is OFF')
elif not exists(ssl_certificate): elif not exists(ssl_certificate):
logger.warning('unable to open SSL certificate. SSL is OFF') logger.warning('unable to open SSL certificate. SSL is OFF')
+4 -4
View File
@@ -10,7 +10,7 @@ Restricted environment to execute application's code
""" """
import sys import sys
from gluon._compat import pickle, ClassType from gluon._compat import pickle, ClassType, unicodeT, to_bytes
import traceback import traceback
import types import types
import os import os
@@ -192,10 +192,10 @@ class RestrictedError(Exception):
# safely show an useful message to the user # safely show an useful message to the user
try: try:
output = self.output output = self.output
if isinstance(output, unicode): if not isinstance(output, str, bytes, bytearray):
output = output.encode("utf8")
elif not isinstance(output, str):
output = str(output) output = str(output)
if isinstance(output, unicodeT):
output = to_bytes(output)
except: except:
output = "" output = ""
return output return output
+2 -2
View File
@@ -119,8 +119,8 @@ def xml(value, encoding='UTF-8', key='document', quote=True):
return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote)) return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote))
def json(value, default=custom_json, indent=None): def json(value, default=custom_json, indent=None, sort_keys=False):
value = json_parser.dumps(value, default=default, sort_keys=True, indent=indent) value = json_parser.dumps(value, default=default, sort_keys=sort_keys, indent=indent)
# replace JavaScript incompatible spacing # replace JavaScript incompatible spacing
# http://timelessrepo.com/json-isnt-a-javascript-subset # http://timelessrepo.com/json-isnt-a-javascript-subset
# PY3 FIXME # PY3 FIXME
+12 -4
View File
@@ -658,7 +658,8 @@ class AutocompleteWidget(object):
orderby=None, limitby=(0, 10), distinct=False, orderby=None, limitby=(0, 10), distinct=False,
keyword='_autocomplete_%(tablename)s_%(fieldname)s', keyword='_autocomplete_%(tablename)s_%(fieldname)s',
min_length=2, help_fields=None, help_string=None, min_length=2, help_fields=None, help_string=None,
at_beginning=True, default_var='ac'): at_beginning=True, default_var='ac', user_signature=True,
hash_vars=False):
self.help_fields = help_fields or [] self.help_fields = help_fields or []
self.help_string = help_string self.help_string = help_string
@@ -683,7 +684,8 @@ class AutocompleteWidget(object):
if hasattr(request, 'application'): if hasattr(request, 'application'):
urlvars = request.vars urlvars = request.vars
urlvars[default_var] = 1 urlvars[default_var] = 1
self.url = URL(args=request.args, vars=urlvars) self.url = URL(args=request.args, vars=urlvars,
user_signature=user_signature, hash_vars=hash_vars)
self.run_callback = True self.run_callback = True
else: else:
self.url = request self.url = request
@@ -1919,8 +1921,10 @@ class SQLFORM(FORM):
if 'table_name' in attributes: if 'table_name' in attributes:
del attributes['table_name'] del attributes['table_name']
return SQLFORM(DAL(None).define_table(table_name, *[field.clone() for field in fields]), # Clone fields, while passing tables straight through
**attributes) fields_with_clones = [f.clone() if isinstance(f, Field) else f for f in fields]
return SQLFORM(DAL(None).define_table(table_name, *fields_with_clones), **attributes)
@staticmethod @staticmethod
def build_query(fields, keywords): def build_query(fields, keywords):
@@ -1935,6 +1939,8 @@ class SQLFORM(FORM):
if settings.global_settings.web2py_runtime_gae: if settings.global_settings.web2py_runtime_gae:
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields]) return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
else: else:
if not (sfields and key and key.split()):
return fields[0].table
return reduce(lambda a,b:a&b,[ return reduce(lambda a,b:a&b,[
reduce(lambda a,b: a|b, [ reduce(lambda a,b: a|b, [
field.contains(k) for field in sfields] field.contains(k) for field in sfields]
@@ -2159,6 +2165,7 @@ class SQLFORM(FORM):
represent_none=None, represent_none=None,
showblobs=False): showblobs=False):
dbset = None
formstyle = formstyle or current.response.formstyle formstyle = formstyle or current.response.formstyle
if isinstance(query, Set): if isinstance(query, Set):
query = query.query query = query.query
@@ -3037,6 +3044,7 @@ class SQLFORM(FORM):
res.view_form = view_form res.view_form = view_form
res.search_form = search_form res.search_form = search_form
res.rows = rows res.rows = rows
res.dbset = dbset
return res return res
@staticmethod @staticmethod
+19 -2
View File
@@ -1320,7 +1320,7 @@ class AuthJWT(object):
# is the following safe or should we use # is the following safe or should we use
# calendar.timegm(datetime.datetime.utcnow().timetuple()) # calendar.timegm(datetime.datetime.utcnow().timetuple())
# result seem to be the same (seconds since epoch, in UTC) # result seem to be the same (seconds since epoch, in UTC)
now = time.mktime(datetime.datetime.now().timetuple()) now = time.mktime(datetime.datetime.utcnow().timetuple())
expires = now + self.expiration expires = now + self.expiration
payload = dict( payload = dict(
hmac_key=session_auth['hmac_key'], hmac_key=session_auth['hmac_key'],
@@ -1332,7 +1332,7 @@ class AuthJWT(object):
return payload return payload
def refresh_token(self, orig_payload): def refresh_token(self, orig_payload):
now = time.mktime(datetime.datetime.now().timetuple()) now = time.mktime(datetime.datetime.utcnow().timetuple())
if self.verify_expiration: if self.verify_expiration:
orig_exp = orig_payload['exp'] orig_exp = orig_payload['exp']
if orig_exp + self.leeway < now: if orig_exp + self.leeway < now:
@@ -1792,6 +1792,7 @@ class Auth(AuthAPI):
servicevalidate='serviceValidate', servicevalidate='serviceValidate',
proxyvalidate='proxyValidate', proxyvalidate='proxyValidate',
logout='logout'), logout='logout'),
cas_create_user=True,
extra_fields={}, extra_fields={},
actions_disabled=[], actions_disabled=[],
controller=controller, controller=controller,
@@ -2284,6 +2285,7 @@ class Auth(AuthAPI):
If the user doesn't yet exist, then they are created. If the user doesn't yet exist, then they are created.
""" """
table_user = self.table_user() table_user = self.table_user()
create_user = self.settings.cas_create_user
user = None user = None
checks = [] checks = []
# make a guess about who this user is # make a guess about who this user is
@@ -2316,6 +2318,11 @@ class Auth(AuthAPI):
update_keys[key] = keys[key] update_keys[key] = keys[key]
user.update_record(**update_keys) user.update_record(**update_keys)
elif checks: elif checks:
if create_user is False:
# Remove current open session a send message
self.logout(next=None, onlogout=None, log=None)
raise HTTP(403, "Forbidden. User need to be created first.")
if 'first_name' not in keys and 'first_name' in table_user.fields: if 'first_name' not in keys and 'first_name' in table_user.fields:
guess = keys.get('email', 'anonymous').split('@')[0] guess = keys.get('email', 'anonymous').split('@')[0]
keys['first_name'] = keys.get('username', guess) keys['first_name'] = keys.get('username', guess)
@@ -3656,6 +3663,16 @@ class Auth(AuthAPI):
if not self.is_logged_in(): if not self.is_logged_in():
redirect(self.settings.login_url, redirect(self.settings.login_url,
client_side=self.settings.client_side) client_side=self.settings.client_side)
# Go to external link to change the password
if self.settings.login_form != self:
cas = self.settings.login_form
# To prevent error if change_password_url function is not defined in alternate login
if hasattr(cas, 'change_password_url'):
next = cas.change_password_url(next)
if next is not None:
redirect(next)
db = self.db db = self.db
table_user = self.table_user() table_user = self.table_user()
s = db(table_user.id == self.user.id) s = db(table_user.id == self.user.id)