forked from orbit-oss/flask
Merge pull request #2635 from pallets/feature/server-name-routing
Require opt-in for subdomain matching
This commit is contained in:
commit
f808c20139
5 changed files with 82 additions and 25 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
44
flask/app.py
44
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
|
with a ``static_folder`` configured.
|
||||||
to None. Required when using ``host_matching=True``
|
:param host_matching: set ``url_map.host_matching`` attribute.
|
||||||
with a ``static_folder`` configured.
|
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.
|
||||||
|
|
@ -1977,19 +1986,30 @@ class Flask(_PackageBoundObject):
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -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>/')
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue