From 46beaece7553333455dfea4e81e3849dad2f147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Jan 2018 17:21:38 +0100 Subject: [PATCH] Implemented pillar.flask_extra.ensure_schema(url) This function ensures that the URL has the correct schema, given the app configuration. This is required because the Flask instance can sit behind an SSL-terminating proxy like HAProxy and not know that it is reachable via HTTPS. --- pillar/flask_extra.py | 18 ++++++++++++++ pillar/web/main/routes.py | 9 ++++--- tests/test_flask_extra.py | 51 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/pillar/flask_extra.py b/pillar/flask_extra.py index c84488f2..d2eda1b9 100644 --- a/pillar/flask_extra.py +++ b/pillar/flask_extra.py @@ -27,3 +27,21 @@ def vary_xhr(): return header_adder(f) return decorator + + +def ensure_schema(url: str) -> str: + """Return the same URL with the configured PREFERRED_URL_SCHEME.""" + import urllib.parse + + if not url: + return url + + bits = urllib.parse.urlsplit(url, allow_fragments=True) + + if not bits[0] and not bits[1]: + # don't replace the schema if there is not even a hostname. + return url + + scheme = flask.current_app.config.get('PREFERRED_URL_SCHEME', 'https') + bits = (scheme, *bits[1:]) + return urllib.parse.urlunsplit(bits) diff --git a/pillar/web/main/routes.py b/pillar/web/main/routes.py index f7912ed6..48d238fb 100644 --- a/pillar/web/main/routes.py +++ b/pillar/web/main/routes.py @@ -1,4 +1,5 @@ import logging +import urllib.parse from pillarsdk import Node from flask import Blueprint @@ -8,6 +9,7 @@ from flask import redirect from flask import request from werkzeug.contrib.atom import AtomFeed +from pillar.flask_extra import ensure_schema from pillar.web.utils import system_util from pillar.web.nodes.routes import url_for_node from pillar.web.nodes.custom.posts import posts_view @@ -92,7 +94,8 @@ def feeds_blogs(): @current_app.cache.cached(60*5) def render_page(): feed = AtomFeed('Blender Cloud - Latest updates', - feed_url=request.url, url=request.url_root) + feed_url=ensure_schema(request.url), + url=ensure_schema(request.url_root)) # Get latest blog posts api = system_util.pillar_api() latest_posts = Node.all({ @@ -106,9 +109,9 @@ def feeds_blogs(): # Populate the feed for post in latest_posts._items: - author = post.user.fullname + author = post.user.fullname or post.user.username updated = post._updated if post._updated else post._created - url = url_for_node(node=post) + url = ensure_schema(urllib.parse.urljoin(request.host_url, url_for_node(node=post))) content = post.properties.content[:500] content = '

{0}... Read more

'.format(content, url) diff --git a/tests/test_flask_extra.py b/tests/test_flask_extra.py index f373afd2..9ecdd7b7 100644 --- a/tests/test_flask_extra.py +++ b/tests/test_flask_extra.py @@ -33,3 +33,54 @@ class FlaskExtraTest(unittest.TestCase): self.assertEqual(201, resp.status_code) self.assertNotIn('Vary', resp.headers) self.assertEqual('nah', resp.data.decode()) + + +class EnsureSchemaTest(unittest.TestCase): + def test_ensure_schema_http(self): + import pillar.flask_extra + + suffix = '://user:password@hostname/some-path/%2Fpaththing?query=abc#fragment' + + app = flask.Flask(__name__) + app.config['PREFERRED_URL_SCHEME'] = 'http' + with app.app_context(): + for scheme in ('http', 'https', 'ftp', 'gopher'): + self.assertEqual( + f'http{suffix}', + pillar.flask_extra.ensure_schema(f'{scheme}{suffix}')) + + def test_ensure_schema_https(self): + import pillar.flask_extra + + suffix = '://user:password@hostname/some-path/%2Fpaththing?query=abc#fragment' + + app = flask.Flask(__name__) + app.config['PREFERRED_URL_SCHEME'] = 'https' + with app.app_context(): + for scheme in ('http', 'https', 'ftp', 'gopher'): + self.assertEqual( + f'https{suffix}', + pillar.flask_extra.ensure_schema(f'{scheme}{suffix}')) + + def test_no_config(self): + import pillar.flask_extra + + suffix = '://user:password@hostname/some-path/%2Fpaththing?query=abc#fragment' + + app = flask.Flask(__name__) + app.config.pop('PREFERRED_URL_SCHEME', None) + with app.app_context(): + self.assertEqual( + f'https{suffix}', + pillar.flask_extra.ensure_schema(f'gopher{suffix}')) + + def test_corner_cases(self): + import pillar.flask_extra + + app = flask.Flask(__name__) + app.config['PREFERRED_URL_SCHEME'] = 'https' + with app.app_context(): + self.assertEqual('', pillar.flask_extra.ensure_schema('')) + self.assertEqual('/some/path/only', pillar.flask_extra.ensure_schema('/some/path/only')) + self.assertEqual('https://hostname/path', + pillar.flask_extra.ensure_schema('//hostname/path'))