Browse Source

Merge pull request #2635 from pallets/feature/server-name-routing

Require opt-in for subdomain matching
pull/2629/head
David Lord 7 years ago committed by GitHub
parent
commit
f808c20139
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGES.rst
  2. 15
      docs/config.rst
  3. 40
      flask/app.py
  4. 35
      tests/test_basic.py
  5. 8
      tests/test_testing.py

5
CHANGES.rst

@ -139,6 +139,10 @@ unreleased
attribute on the session cookie. (`#2607`_) attribute on the session cookie. (`#2607`_)
- Added :meth:`~flask.Flask.test_cli_runner` to create a Click runner - Added :meth:`~flask.Flask.test_cli_runner` to create a Click runner
that can invoke Flask CLI commands for testing. (`#2636`_) that can invoke Flask CLI commands for testing. (`#2636`_)
- Subdomain matching is disabled by default and setting
:data:`SERVER_NAME` does not implicily enable it. It can be enabled by
passing ``subdomain_matching=True`` to the ``Flask`` constructor.
(`#2635`_)
.. _pallets/meta#24: https://github.com/pallets/meta/issues/24 .. _pallets/meta#24: https://github.com/pallets/meta/issues/24
.. _#1421: https://github.com/pallets/flask/issues/1421 .. _#1421: https://github.com/pallets/flask/issues/1421
@ -181,6 +185,7 @@ unreleased
.. _#2606: https://github.com/pallets/flask/pull/2606 .. _#2606: https://github.com/pallets/flask/pull/2606
.. _#2607: https://github.com/pallets/flask/pull/2607 .. _#2607: https://github.com/pallets/flask/pull/2607
.. _#2636: https://github.com/pallets/flask/pull/2636 .. _#2636: https://github.com/pallets/flask/pull/2636
.. _#2635: https://github.com/pallets/flask/pull/2635
Version 0.12.2 Version 0.12.2

15
docs/config.rst

@ -181,8 +181,8 @@ The following configuration values are used internally by Flask:
.. py:data:: SESSION_COOKIE_DOMAIN .. py:data:: SESSION_COOKIE_DOMAIN
The domain match rule that the session cookie will be valid for. If not The domain match rule that the session cookie will be valid for. If not
set, the cookie will be valid for all subdomains of ``SERVER_NAME``. If set, the cookie will be valid for all subdomains of :data:`SERVER_NAME`.
``False``, the cookie's domain will not be set. If ``False``, the cookie's domain will not be set.
Default: ``None`` Default: ``None``
@ -257,13 +257,14 @@ The following configuration values are used internally by Flask:
.. py:data:: SERVER_NAME .. py:data:: SERVER_NAME
Inform the application what host and port it is bound to. Required for Inform the application what host and port it is bound to. Required
subdomain route matching support. for subdomain route matching support.
If set, will be used for the session cookie domain if If set, will be used for the session cookie domain if
``SESSION_COOKIE_DOMAIN`` is not set. Modern web browsers will not allow :data:`SESSION_COOKIE_DOMAIN` is not set. Modern web browsers will
setting cookies for domains without a dot. To use a domain locally, not allow setting cookies for domains without a dot. To use a domain
add any names that should route to the app to your ``hosts`` file. :: locally, add any names that should route to the app to your
``hosts`` file. ::
127.0.0.1 localhost.dev 127.0.0.1 localhost.dev

40
flask/app.py

@ -123,8 +123,13 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.11 .. versionadded:: 0.11
The `root_path` parameter was added. The `root_path` parameter was added.
.. versionadded:: 0.13 .. versionadded:: 1.0
The `host_matching` and `static_host` parameters were added. The ``host_matching`` and ``static_host`` parameters were added.
.. versionadded:: 1.0
The ``subdomain_matching`` parameter was added. Subdomain
matching needs to be enabled manually now. Setting
:data:`SERVER_NAME` does not implicitly enable it.
:param import_name: the name of the application package :param import_name: the name of the application package
:param static_url_path: can be used to specify a different path for the :param static_url_path: can be used to specify a different path for the
@ -133,11 +138,13 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served :param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'`` at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application. folder in the root path of the application.
:param host_matching: sets the app's ``url_map.host_matching`` to the given :param static_host: the host to use when adding the static route.
value. Defaults to False. Defaults to None. Required when using ``host_matching=True``
:param static_host: the host to use when adding the static route. Defaults
to None. Required when using ``host_matching=True``
with a ``static_folder`` configured. with a ``static_folder`` configured.
:param host_matching: set ``url_map.host_matching`` attribute.
Defaults to False.
:param subdomain_matching: consider the subdomain relative to
:data:`SERVER_NAME` when matching routes. Defaults to False.
:param template_folder: the folder that contains the templates that should :param template_folder: the folder that contains the templates that should
be used by the application. Defaults to be used by the application. Defaults to
``'templates'`` folder in the root path of the ``'templates'`` folder in the root path of the
@ -347,6 +354,7 @@ class Flask(_PackageBoundObject):
static_folder='static', static_folder='static',
static_host=None, static_host=None,
host_matching=False, host_matching=False,
subdomain_matching=False,
template_folder='templates', template_folder='templates',
instance_path=None, instance_path=None,
instance_relative_config=False, instance_relative_config=False,
@ -530,6 +538,7 @@ class Flask(_PackageBoundObject):
self.url_map = Map() self.url_map = Map()
self.url_map.host_matching = host_matching self.url_map.host_matching = host_matching
self.subdomain_matching = subdomain_matching
# tracks internally if the application already handled at least one # tracks internally if the application already handled at least one
# request. # request.
@ -1978,18 +1987,29 @@ class Flask(_PackageBoundObject):
def create_url_adapter(self, request): def create_url_adapter(self, request):
"""Creates a URL adapter for the given request. The URL adapter """Creates a URL adapter for the given request. The URL adapter
is created at a point where the request context is not yet set up is created at a point where the request context is not yet set
so the request is passed explicitly. up so the request is passed explicitly.
.. versionadded:: 0.6 .. versionadded:: 0.6
.. versionchanged:: 0.9 .. versionchanged:: 0.9
This can now also be called without a request object when the This can now also be called without a request object when the
URL adapter is created for the application context. URL adapter is created for the application context.
.. versionchanged:: 1.0
:data:`SERVER_NAME` no longer implicitly enables subdomain
matching. Use :attr:`subdomain_matching` instead.
""" """
if request is not None: if request is not None:
return self.url_map.bind_to_environ(request.environ, # If subdomain matching is disabled (the default), use the
server_name=self.config['SERVER_NAME']) # default subdomain in all cases. This should be the default
# in Werkzeug but it currently does not have that feature.
subdomain = ((self.url_map.default_subdomain or None)
if not self.subdomain_matching else None)
return self.url_map.bind_to_environ(
request.environ,
server_name=self.config['SERVER_NAME'],
subdomain=subdomain)
# We need at the very least the server name to be set for this # We need at the very least the server name to be set for this
# to work. # to work.
if self.config['SERVER_NAME'] is not None: if self.config['SERVER_NAME'] is not None:

35
tests/test_basic.py

@ -1429,10 +1429,12 @@ def test_request_locals():
assert not flask.g assert not flask.g
def test_test_app_proper_environ(app, client): def test_test_app_proper_environ():
app = flask.Flask(__name__, subdomain_matching=True)
app.config.update( app.config.update(
SERVER_NAME='localhost.localdomain:5000' SERVER_NAME='localhost.localdomain:5000'
) )
client = app.test_client()
@app.route('/') @app.route('/')
def index(): def index():
@ -1783,8 +1785,10 @@ def test_g_iteration_protocol(app_ctx):
assert sorted(flask.g) == ['bar', 'foo'] assert sorted(flask.g) == ['bar', 'foo']
def test_subdomain_basic_support(app, client): def test_subdomain_basic_support():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'localhost.localdomain' app.config['SERVER_NAME'] = 'localhost.localdomain'
client = app.test_client()
@app.route('/') @app.route('/')
def normal_index(): def normal_index():
@ -1801,7 +1805,9 @@ def test_subdomain_basic_support(app, client):
assert rv.data == b'test index' assert rv.data == b'test index'
def test_subdomain_matching(app, client): def test_subdomain_matching():
app = flask.Flask(__name__, subdomain_matching=True)
client = app.test_client()
app.config['SERVER_NAME'] = 'localhost.localdomain' app.config['SERVER_NAME'] = 'localhost.localdomain'
@app.route('/', subdomain='<user>') @app.route('/', subdomain='<user>')
@ -1812,8 +1818,10 @@ def test_subdomain_matching(app, client):
assert rv.data == b'index for mitsuhiko' assert rv.data == b'index for mitsuhiko'
def test_subdomain_matching_with_ports(app, client): def test_subdomain_matching_with_ports():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'localhost.localdomain:3000' app.config['SERVER_NAME'] = 'localhost.localdomain:3000'
client = app.test_client()
@app.route('/', subdomain='<user>') @app.route('/', subdomain='<user>')
def index(user): def index(user):
@ -1823,6 +1831,25 @@ def test_subdomain_matching_with_ports(app, client):
assert rv.data == b'index for mitsuhiko' assert rv.data == b'index for mitsuhiko'
@pytest.mark.parametrize('matching', (False, True))
def test_subdomain_matching_other_name(matching):
app = flask.Flask(__name__, subdomain_matching=matching)
app.config['SERVER_NAME'] = 'localhost.localdomain:3000'
client = app.test_client()
@app.route('/')
def index():
return '', 204
# ip address can't match name
rv = client.get('/', 'http://127.0.0.1:3000/')
assert rv.status_code == 404 if matching else 204
# allow all subdomains if matching is disabled
rv = client.get('/', 'http://www.localhost.localdomain:3000/')
assert rv.status_code == 404 if matching else 204
def test_multi_route_rules(app, client): def test_multi_route_rules(app, client):
@app.route('/') @app.route('/')
@app.route('/<test>/') @app.route('/<test>/')

8
tests/test_testing.py

@ -114,9 +114,11 @@ def test_path_is_url(app):
assert eb.path == '/' assert eb.path == '/'
def test_blueprint_with_subdomain(app, client): def test_blueprint_with_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'example.com:1234' app.config['SERVER_NAME'] = 'example.com:1234'
app.config['APPLICATION_ROOT'] = '/foo' app.config['APPLICATION_ROOT'] = '/foo'
client = app.test_client()
bp = flask.Blueprint('company', __name__, subdomain='xxx') bp = flask.Blueprint('company', __name__, subdomain='xxx')
@ -304,8 +306,10 @@ def test_json_request_and_response(app, client):
assert rv.get_json() == json_data assert rv.get_json() == json_data
def test_subdomain(app, client): def test_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'example.com' app.config['SERVER_NAME'] = 'example.com'
client = app.test_client()
@app.route('/', subdomain='<company_id>') @app.route('/', subdomain='<company_id>')
def view(company_id): def view(company_id):

Loading…
Cancel
Save