From 76f3384aae23df6d59ffdf6d99515ba6a9cbc559 Mon Sep 17 00:00:00 2001 From: Tim Nyborg Date: Thu, 1 Feb 2018 14:04:47 +0000 Subject: [PATCH 01/15] Allow choosing a saml entityid Allows you to pick an entityid when using federation XML data or an MDQ with many entries. Pysaml2's MDQ metadata is loaded lazily, after you provide a key, so it's necessary to pass the entityid as a key --- gluon/contrib/login_methods/saml2_auth.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gluon/contrib/login_methods/saml2_auth.py b/gluon/contrib/login_methods/saml2_auth.py index 462d9845..d80a13ba 100644 --- a/gluon/contrib/login_methods/saml2_auth.py +++ b/gluon/contrib/login_methods/saml2_auth.py @@ -104,11 +104,12 @@ def obj2dict(obj, processed=None): types.BuiltinFunctionType, types.BuiltinMethodType)) -def saml2_handler(session, request, config_filename = None): +def saml2_handler(session, request, config_filename = None, entityid = None): config_filename = config_filename or os.path.join(request.folder,'private','sp_conf') client = Saml2Client(config_file = config_filename) - idps = client.metadata.with_descriptor("idpsso") - entityid = idps.keys()[0] + if not entityid: + idps = client.metadata.with_descriptor("idpsso") + entityid = idps.keys()[0] bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST] binding, destination = client.pick_binding( "single_sign_on_service", bindings, "idpsso", entity_id=entityid) @@ -145,7 +146,7 @@ class Saml2Auth(object): 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], user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0], - ), logout_url=None, change_password_url=None): + ), logout_url=None, change_password_url=None, entityid=None): self.config_file = config_file self.maps = maps @@ -154,9 +155,12 @@ class Saml2Auth(object): # URL to let users change their password in the IDP system self.saml_change_password_url = change_password_url + + # URL to specify an IDP if using federation metadata or an MDQ + self.entityid = entityid def login_url(self, next="/"): - d = saml2_handler(current.session, current.request) + d = saml2_handler(current.session, current.request, entityid=self.entityid) if 'url' in d: redirect(d['url']) elif 'error' in d: From 5dcbae0b379cc78373cb55a84d66e96cfb2199e1 Mon Sep 17 00:00:00 2001 From: Tim Nyborg Date: Fri, 2 Feb 2018 11:03:04 +0000 Subject: [PATCH 02/15] Update saml2_auth.py Pass along any _next url var as part of the outstanding queries, so web2py will know where to send the user once they come back from singing on. Useful if the SAML auth is part of a CAS, because otherwise the user is sent from the CAS consumer -> CAS -> SSO -> CAS, and is never returned to the consumer application --- gluon/contrib/login_methods/saml2_auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gluon/contrib/login_methods/saml2_auth.py b/gluon/contrib/login_methods/saml2_auth.py index d80a13ba..9439443d 100644 --- a/gluon/contrib/login_methods/saml2_auth.py +++ b/gluon/contrib/login_methods/saml2_auth.py @@ -121,6 +121,8 @@ def saml2_handler(session, request, config_filename = None, entityid = None): req_id, req = client.create_authn_request(destination, binding=binding) relay_state = web2py_uuid().replace('-','') session.saml_outstanding_queries = {req_id: request.url} + if '_next' in request.vars: + session.saml_outstanding_queries += '?%s' % request.vars._next session.saml_req_id = req_id http_args = client.apply_binding(binding, str(req), destination, relay_state=relay_state) From 072311fd2c8a6e404cb80c7c24af60e119477baa Mon Sep 17 00:00:00 2001 From: Tim Nyborg Date: Fri, 2 Feb 2018 11:23:48 +0000 Subject: [PATCH 03/15] Revert Committed in error --- gluon/contrib/login_methods/saml2_auth.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gluon/contrib/login_methods/saml2_auth.py b/gluon/contrib/login_methods/saml2_auth.py index 9439443d..ea650463 100644 --- a/gluon/contrib/login_methods/saml2_auth.py +++ b/gluon/contrib/login_methods/saml2_auth.py @@ -120,9 +120,7 @@ def saml2_handler(session, request, config_filename = None, entityid = None): if not request.vars.SAMLResponse: req_id, req = client.create_authn_request(destination, binding=binding) relay_state = web2py_uuid().replace('-','') - session.saml_outstanding_queries = {req_id: request.url} - if '_next' in request.vars: - session.saml_outstanding_queries += '?%s' % request.vars._next + session.saml_outstanding_queries = {req_id: request.url} session.saml_req_id = req_id http_args = client.apply_binding(binding, str(req), destination, relay_state=relay_state) From 421aec162a828efb9d105f2585f7133e06a479da Mon Sep 17 00:00:00 2001 From: ilvalle Date: Sun, 4 Feb 2018 09:41:57 +0100 Subject: [PATCH 04/15] fix py3 STYLE and SCRIPT tags, close #1835 --- gluon/html.py | 16 +++++++++------- gluon/tests/test_html.py | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/gluon/html.py b/gluon/html.py index 73eae9b8..4adf287c 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -1419,20 +1419,21 @@ class LINK(DIV): class SCRIPT(DIV): - tag = 'script' + tag = b'script' + tagname = to_bytes(tag) def xml(self): (fa, co) = self._xml() - fa = to_native(fa) + fa = to_bytes(fa) # no escaping of subcomponents - co = '\n'.join([str(component) for component in + co = b'\n'.join([to_bytes(component) for component in self.components]) if co: # # return '<%s%s>' % (self.tag, fa, co, self.tag) - return '<%s%s>' % (self.tag, fa, co, self.tag) + return b'<%s%s>' % (self.tagname, fa, co, self.tagname) else: return DIV.xml(self) @@ -1440,18 +1441,19 @@ class SCRIPT(DIV): class STYLE(DIV): tag = 'style' + tagname = to_bytes(tag) def xml(self): (fa, co) = self._xml() - fa = to_native(fa) + fa = to_bytes(fa) # no escaping of subcomponents - co = '\n'.join([str(component) for component in + co = b'\n'.join([to_bytes(component) for component in self.components]) if co: # - return '<%s%s>' % (self.tag, fa, co, self.tag) + return b'<%s%s>' % (self.tagname, fa, co, self.tagname) else: return DIV.xml(self) diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py index 26693efb..4cca19e2 100644 --- a/gluon/tests/test_html.py +++ b/gluon/tests/test_html.py @@ -340,18 +340,20 @@ class TestBareHelpers(unittest.TestCase): def test_SCRIPT(self): self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(), - '''''') self.assertEqual(SCRIPT('<>').xml(), - '''''') self.assertEqual(SCRIPT().xml(), b'') + self.assertEqual(SCRIPT(';').xml() + DIV().xml(), + b'
') def test_STYLE(self): self.assertEqual(STYLE('<>', _a='1', _b='2').xml(), - '') + b'') # Try to hit : return DIV.xml(self) self.assertEqual(STYLE().xml(), b'') From ad3c69155b7ba3cc2a649fa5f9618f8ca7903789 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Sun, 4 Feb 2018 09:58:16 +0100 Subject: [PATCH 05/15] fix few urllib.urlencode, close #1841 --- applications/admin/controllers/pythonanywhere.py | 5 ++--- gluon/contrib/login_methods/rpx_account.py | 5 ++--- gluon/sqlhtml.py | 7 +++---- gluon/tools.py | 9 ++++----- scripts/access.wsgi | 12 +++++++++--- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/applications/admin/controllers/pythonanywhere.py b/applications/admin/controllers/pythonanywhere.py index e61b06ab..d9989cee 100644 --- a/applications/admin/controllers/pythonanywhere.py +++ b/applications/admin/controllers/pythonanywhere.py @@ -5,7 +5,7 @@ import re import gzip import tarfile from gluon.contrib.simplejsonrpc import ServerProxy -from gluon._compat import StringIO, ProtocolError +from gluon._compat import StringIO, ProtocolError, urlencode, urllib2 def deploy(): response.title = T('Deploy to pythonanywhere') @@ -26,9 +26,8 @@ def create_account(): except ProtocolError as error: pass - import urllib, urllib2 url = 'https://www.pythonanywhere.com/api/web2py/create_account' - data = urllib.urlencode(request.vars) + data = urlencode(request.vars) req = urllib2.Request(url, data) try: diff --git a/gluon/contrib/login_methods/rpx_account.py b/gluon/contrib/login_methods/rpx_account.py index dcfdc114..e79d17df 100644 --- a/gluon/contrib/login_methods/rpx_account.py +++ b/gluon/contrib/login_methods/rpx_account.py @@ -13,12 +13,11 @@ import os import re -import urllib from gluon import * from gluon.tools import fetch from gluon.storage import Storage import json - +from gluon._compat import urlencode class RPXAccount(object): @@ -83,7 +82,7 @@ class RPXAccount(object): token = request.post_vars.token or request.get_vars.token if token: user = Storage() - data = urllib.urlencode( + data = urlencode( dict(apiKey=self.api_key, token=token)) auth_info_json = fetch(self.auth_url + '?' + data) auth_info = json.loads(auth_info_json) diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index aef106ff..b4ba4051 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -15,12 +15,11 @@ Holds: """ import datetime -import urllib import re import copy import os -from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode +from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode, urlencode from gluon.http import HTTP, redirect from gluon.html import XmlComponent, truncate_string from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG @@ -3550,7 +3549,7 @@ class SQLTABLE(TABLE): if ref.find('.') >= 0: tref, fref = ref.split('.') if hasattr(sqlrows.db[tref], '_primarykey'): - href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref: r})) + href = '%s/%s?%s' % (linkto, tref, urlencode({fref: r})) r = A(represent(field, r, record), _href=str(href)) elif field.represent: if field not in repr_cache: @@ -3561,7 +3560,7 @@ class SQLTABLE(TABLE): elif linkto and hasattr(field._table, '_primarykey')\ and fieldname in field._table._primarykey: # have to test this with multi-key tables - key = urllib.urlencode(dict([ + key = urlencode(dict([ ((tablename in record and isinstance(record, Row) and isinstance(record[tablename], Row)) and diff --git a/gluon/tools.py b/gluon/tools.py index 716dafcd..c138242d 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -12,7 +12,7 @@ Auth, Mail, PluginManager and various utilities import base64 from functools import reduce -from gluon._compat import pickle, thread, urllib2, Cookie, StringIO +from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, urlencode from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems from gluon._compat import to_bytes, to_native, add_charset @@ -27,7 +27,6 @@ import time import fnmatch import traceback import smtplib -import urllib import email.utils import random import hmac @@ -873,7 +872,7 @@ class Recaptcha(DIV): and len(recaptcha_challenge_field)): self.errors['captcha'] = self.error_message return False - params = urllib.urlencode({ + params = urlencode({ 'privatekey': private_key, 'remoteip': remoteip, 'challenge': recaptcha_challenge_field, @@ -1026,7 +1025,7 @@ class Recaptcha2(DIV): if not recaptcha_response_field: self.errors['captcha'] = self.error_message return False - params = urllib.urlencode({ + params = urlencode({ 'secret': self.private_key, 'remoteip': remoteip, 'response': recaptcha_response_field, @@ -4738,7 +4737,7 @@ def fetch(url, data=None, headers=None, user_agent='Mozilla/5.0'): headers = headers or {} if data is not None: - data = urllib.urlencode(data) + data = urlencode(data) if user_agent: headers['User-agent'] = user_agent headers['Cookie'] = ' '.join( diff --git a/scripts/access.wsgi b/scripts/access.wsgi index 86285070..8cc6b40b 100644 --- a/scripts/access.wsgi +++ b/scripts/access.wsgi @@ -35,11 +35,17 @@ # URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access' +PY2 = sys.version_info[0] == 2 def allow_access(environ,host): + if PY2: + import urllib2 + from urllib import urlencode + else: + from urllib import request as urllib2 + from urllib.parse import urlencode + import os - import urllib - import urllib2 import datetime header = '%s @ %s ' % (datetime.datetime.now(),host) + '='*20 pprint = '\n'.join('%s:%s' % item for item in environ.items()) @@ -56,7 +62,7 @@ def allow_access(environ,host): if key.startswith('HTTP_'): headers[key[5:]] = environ[key] # this passes the cookies through! try: - data = urllib.urlencode({'request_uri':environ['REQUEST_URI']}) + data = urlencode({'request_uri':environ['REQUEST_URI']}) request = urllib2.Request(URL_CHECK_ACCESS % dict(app=app),data,headers) response = urllib2.urlopen(request).read().strip().lower() if response.startswith('true'): return True From d1efc8b55db45af9e8a602539460e3b9f3bc070b Mon Sep 17 00:00:00 2001 From: ilvalle Date: Sun, 4 Feb 2018 10:02:14 +0100 Subject: [PATCH 06/15] fix py37 conflict due to async definition in scheduler --- gluon/scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 710815db..79d22c0f 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -525,7 +525,7 @@ class MetaScheduler(threading.Thread): self.have_heartbeat = True # set to False to kill self.empty_runs = 0 - def async(self, task): + def local_async(self, task): """Start the background process. Args: @@ -913,7 +913,7 @@ class Scheduler(MetaScheduler): self.w_stats.empty_runs = 0 self.w_stats.status = RUNNING self.w_stats.total += 1 - self.wrapped_report_task(task, self.async(task)) + self.wrapped_report_task(task, self.local_async(task)) if not self.w_stats.status == DISABLED: self.w_stats.status = ACTIVE else: From 521d5bce97a1ed706057ad67663fc7e3d63a1798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonel=20C=C3=A2mara?= Date: Tue, 6 Feb 2018 19:48:11 +0000 Subject: [PATCH 07/15] fix print statements in a couple of scripts --- scripts/tickets2db.py | 2 +- scripts/tickets2slack.py | 2 +- scripts/zip_static_files.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/tickets2db.py b/scripts/tickets2db.py index ecb1b4cd..da499483 100755 --- a/scripts/tickets2db.py +++ b/scripts/tickets2db.py @@ -31,7 +31,7 @@ hashes = {} while 1: if request.tickets_db: - print "You're storing tickets yet in database" + print("You're storing tickets yet in database") sys.exit(1) for file in os.listdir(errors_path): diff --git a/scripts/tickets2slack.py b/scripts/tickets2slack.py index 692dce2b..c9d181bb 100755 --- a/scripts/tickets2slack.py +++ b/scripts/tickets2slack.py @@ -22,7 +22,7 @@ import json try: import requests except ImportError as e: - print "missing module 'Requests', aborting." + print("missing module 'Requests', aborting.") sys.exit(1) from gluon import URL diff --git a/scripts/zip_static_files.py b/scripts/zip_static_files.py index 57054e5b..170ef12a 100644 --- a/scripts/zip_static_files.py +++ b/scripts/zip_static_files.py @@ -14,7 +14,7 @@ def zip_static(filelist=[]): extension = os.path.splitext(fi) extension = len(extension) > 1 and extension[1] or None if not extension or extension not in ALLOWED_EXTS: - print 'skipping %s' % os.path.basename(fi) + print('skipping %s' % os.path.basename(fi)) continue fstats = os.stat(fi) atime, mtime = fstats.st_atime, fstats.st_mtime @@ -23,10 +23,10 @@ def zip_static(filelist=[]): zstats = os.stat(gfi) zatime, zmtime = zstats.st_atime, zstats.st_mtime if zatime == atime and zmtime == mtime: - print 'skipping %s, already gzipped to the latest version' % os.path.basename(fi) + print('skipping %s, already gzipped to the latest version' % os.path.basename(fi)) continue - print 'gzipping %s to %s' % ( - os.path.basename(fi), os.path.basename(gfi)) + print('gzipping %s to %s' % ( + os.path.basename(fi), os.path.basename(gfi))) f_in = open(fi, 'rb') f_out = gzip.open(gfi, 'wb') f_out.writelines(f_in) @@ -36,7 +36,7 @@ def zip_static(filelist=[]): saved = fstats.st_size - os.stat(gfi).st_size tsave += saved - print 'saved %s KB' % (int(tsave) / 1000.0) + print('saved %s KB' % (int(tsave) / 1000.0)) if __name__ == '__main__': ALLOWED_EXTS = ['.css', '.js'] From 009d5ce48c14ed6cc9f4ae5fb1334ee7ae3507c7 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Thu, 8 Feb 2018 20:15:04 +0100 Subject: [PATCH 08/15] fix layout, close #1828, thanks @carpaIdea --- applications/welcome/views/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/welcome/views/layout.html b/applications/welcome/views/layout.html index ab477415..9f664c45 100644 --- a/applications/welcome/views/layout.html +++ b/applications/welcome/views/layout.html @@ -39,7 +39,7 @@ web2py -