Skip to content

Commit 10bdf61

Browse files
authored
setting SERVER_NAME does not restrict routing for both subdomain_matching and host_matching (#5634)
2 parents 07c7d57 + 4995a77 commit 10bdf61

File tree

5 files changed

+98
-43
lines changed

5 files changed

+98
-43
lines changed

CHANGES.rst

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Unreleased
2323
- Support key rotation with the ``SECRET_KEY_FALLBACKS`` config, a list of old
2424
secret keys that can still be used for unsigning. Extensions will need to
2525
add support. :issue:`5621`
26+
- Fix how setting ``host_matching=True`` or ``subdomain_matching=False``
27+
interacts with ``SERVER_NAME``. Setting ``SERVER_NAME`` no longer restricts
28+
requests to only that domain. :issue:`5553`
2629

2730

2831
Version 3.0.3

docs/config.rst

+13-4
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,23 @@ The following configuration values are used internally by Flask:
260260

261261
.. py:data:: SERVER_NAME
262262
263-
Inform the application what host and port it is bound to. Required
264-
for subdomain route matching support.
263+
Inform the application what host and port it is bound to.
265264

266-
If set, ``url_for`` can generate external URLs with only an application
267-
context instead of a request context.
265+
Must be set if ``subdomain_matching`` is enabled, to be able to extract the
266+
subdomain from the request.
267+
268+
Must be set for ``url_for`` to generate external URLs outside of a
269+
request context.
268270

269271
Default: ``None``
270272

273+
.. versionchanged:: 3.1
274+
Does not restrict requests to only this domain, for both
275+
``subdomain_matching`` and ``host_matching``.
276+
277+
.. versionchanged:: 1.0
278+
Does not implicitly enable ``subdomain_matching``.
279+
271280
.. versionchanged:: 2.3
272281
Does not affect ``SESSION_COOKIE_DOMAIN``.
273282

src/flask/app.py

+25-17
Original file line numberDiff line numberDiff line change
@@ -425,32 +425,40 @@ def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
425425
is created at a point where the request context is not yet set
426426
up so the request is passed explicitly.
427427
428-
.. versionadded:: 0.6
429-
430-
.. versionchanged:: 0.9
431-
This can now also be called without a request object when the
432-
URL adapter is created for the application context.
428+
.. versionchanged:: 3.1
429+
If :data:`SERVER_NAME` is set, it does not restrict requests to
430+
only that domain, for both ``subdomain_matching`` and
431+
``host_matching``.
433432
434433
.. versionchanged:: 1.0
435434
:data:`SERVER_NAME` no longer implicitly enables subdomain
436435
matching. Use :attr:`subdomain_matching` instead.
436+
437+
.. versionchanged:: 0.9
438+
This can be called outside a request when the URL adapter is created
439+
for an application context.
440+
441+
.. versionadded:: 0.6
437442
"""
438443
if request is not None:
439-
# If subdomain matching is disabled (the default), use the
440-
# default subdomain in all cases. This should be the default
441-
# in Werkzeug but it currently does not have that feature.
442-
if not self.subdomain_matching:
443-
subdomain = self.url_map.default_subdomain or None
444-
else:
445-
subdomain = None
444+
subdomain = None
445+
server_name = self.config["SERVER_NAME"]
446+
447+
if self.url_map.host_matching:
448+
# Don't pass SERVER_NAME, otherwise it's used and the actual
449+
# host is ignored, which breaks host matching.
450+
server_name = None
451+
elif not self.subdomain_matching:
452+
# Werkzeug doesn't implement subdomain matching yet. Until then,
453+
# disable it by forcing the current subdomain to the default, or
454+
# the empty string.
455+
subdomain = self.url_map.default_subdomain or ""
446456

447457
return self.url_map.bind_to_environ(
448-
request.environ,
449-
server_name=self.config["SERVER_NAME"],
450-
subdomain=subdomain,
458+
request.environ, server_name=server_name, subdomain=subdomain
451459
)
452-
# We need at the very least the server name to be set for this
453-
# to work.
460+
461+
# Need at least SERVER_NAME to match/build outside a request.
454462
if self.config["SERVER_NAME"] is not None:
455463
return self.url_map.bind(
456464
self.config["SERVER_NAME"],

tests/test_basic.py

+43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uuid
55
import warnings
66
import weakref
7+
from contextlib import nullcontext
78
from datetime import datetime
89
from datetime import timezone
910
from platform import python_implementation
@@ -1483,6 +1484,48 @@ def test_request_locals():
14831484
assert not flask.g
14841485

14851486

1487+
@pytest.mark.parametrize(
1488+
("subdomain_matching", "host_matching", "expect_base", "expect_abc", "expect_xyz"),
1489+
[
1490+
(False, False, "default", "default", "default"),
1491+
(True, False, "default", "abc", "<invalid>"),
1492+
(False, True, "default", "abc", "default"),
1493+
],
1494+
)
1495+
def test_server_name_matching(
1496+
subdomain_matching: bool,
1497+
host_matching: bool,
1498+
expect_base: str,
1499+
expect_abc: str,
1500+
expect_xyz: str,
1501+
) -> None:
1502+
app = flask.Flask(
1503+
__name__,
1504+
subdomain_matching=subdomain_matching,
1505+
host_matching=host_matching,
1506+
static_host="example.test" if host_matching else None,
1507+
)
1508+
app.config["SERVER_NAME"] = "example.test"
1509+
1510+
@app.route("/", defaults={"name": "default"}, host="<name>")
1511+
@app.route("/", subdomain="<name>", host="<name>.example.test")
1512+
def index(name: str) -> str:
1513+
return name
1514+
1515+
client = app.test_client()
1516+
1517+
r = client.get(base_url="http://example.test")
1518+
assert r.text == expect_base
1519+
1520+
r = client.get(base_url="http://abc.example.test")
1521+
assert r.text == expect_abc
1522+
1523+
with pytest.warns() if subdomain_matching else nullcontext():
1524+
r = client.get(base_url="http://xyz.other.test")
1525+
1526+
assert r.text == expect_xyz
1527+
1528+
14861529
def test_server_name_subdomain():
14871530
app = flask.Flask(__name__, subdomain_matching=True)
14881531
client = app.test_client()

tests/test_blueprints.py

+14-22
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,10 @@ def index():
951951

952952

953953
def test_nesting_subdomains(app, client) -> None:
954-
subdomain = "api"
954+
app.subdomain_matching = True
955+
app.config["SERVER_NAME"] = "example.test"
956+
client.allow_subdomain_redirects = True
957+
955958
parent = flask.Blueprint("parent", __name__)
956959
child = flask.Blueprint("child", __name__)
957960

@@ -960,42 +963,31 @@ def index():
960963
return "child"
961964

962965
parent.register_blueprint(child)
963-
app.register_blueprint(parent, subdomain=subdomain)
964-
965-
client.allow_subdomain_redirects = True
966-
967-
domain_name = "domain.tld"
968-
app.config["SERVER_NAME"] = domain_name
969-
response = client.get("/child/", base_url="http://api." + domain_name)
966+
app.register_blueprint(parent, subdomain="api")
970967

968+
response = client.get("/child/", base_url="http://api.example.test")
971969
assert response.status_code == 200
972970

973971

974972
def test_child_and_parent_subdomain(app, client) -> None:
975-
child_subdomain = "api"
976-
parent_subdomain = "parent"
973+
app.subdomain_matching = True
974+
app.config["SERVER_NAME"] = "example.test"
975+
client.allow_subdomain_redirects = True
976+
977977
parent = flask.Blueprint("parent", __name__)
978-
child = flask.Blueprint("child", __name__, subdomain=child_subdomain)
978+
child = flask.Blueprint("child", __name__, subdomain="api")
979979

980980
@child.route("/")
981981
def index():
982982
return "child"
983983

984984
parent.register_blueprint(child)
985-
app.register_blueprint(parent, subdomain=parent_subdomain)
986-
987-
client.allow_subdomain_redirects = True
988-
989-
domain_name = "domain.tld"
990-
app.config["SERVER_NAME"] = domain_name
991-
response = client.get(
992-
"/", base_url=f"http://{child_subdomain}.{parent_subdomain}.{domain_name}"
993-
)
985+
app.register_blueprint(parent, subdomain="parent")
994986

987+
response = client.get("/", base_url="http://api.parent.example.test")
995988
assert response.status_code == 200
996989

997-
response = client.get("/", base_url=f"http://{parent_subdomain}.{domain_name}")
998-
990+
response = client.get("/", base_url="http://parent.example.test")
999991
assert response.status_code == 404
1000992

1001993

0 commit comments

Comments
 (0)