Skip to content

Commit

Permalink
fix subdomain_matching=False behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Nov 12, 2024
1 parent 07c7d57 commit b04dfc4
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 34 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Unreleased
- Support key rotation with the ``SECRET_KEY_FALLBACKS`` config, a list of old
secret keys that can still be used for unsigning. Extensions will need to
add support. :issue:`5621`
- Fix how setting ``host_matching=True`` or ``subdomain_matching=False``
interacts with ``SERVER_NAME``. Setting ``SERVER_NAME`` no longer restricts
requests to only that domain. :issue:`5553`


Version 3.0.3
Expand Down
27 changes: 15 additions & 12 deletions src/flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,21 +436,24 @@ def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
matching. Use :attr:`subdomain_matching` instead.
"""
if request is not None:
# If subdomain matching is disabled (the default), use the
# default subdomain in all cases. This should be the default
# in Werkzeug but it currently does not have that feature.
if not self.subdomain_matching:
subdomain = self.url_map.default_subdomain or None
else:
subdomain = None
subdomain = None
server_name = self.config["SERVER_NAME"]

if self.url_map.host_matching:
# Don't pass SERVER_NAME, otherwise it's used and the actual
# host is ignored, which breaks host matching.
server_name = None
elif not self.subdomain_matching:
# Werkzeug doesn't implement subdomain matching yet. Until then,
# disable it by forcing the current subdomain to the default, or
# the empty string.
subdomain = self.url_map.default_subdomain or ""

return self.url_map.bind_to_environ(
request.environ,
server_name=self.config["SERVER_NAME"],
subdomain=subdomain,
request.environ, server_name=server_name, subdomain=subdomain
)
# We need at the very least the server name to be set for this
# to work.

# Need at least SERVER_NAME to match/build outside a request.
if self.config["SERVER_NAME"] is not None:
return self.url_map.bind(
self.config["SERVER_NAME"],
Expand Down
46 changes: 46 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import uuid
import warnings
import weakref
from contextlib import nullcontext
from datetime import datetime
from datetime import timezone
from platform import python_implementation
Expand Down Expand Up @@ -1483,6 +1484,51 @@ def test_request_locals():
assert not flask.g


@pytest.mark.parametrize(
("subdomain_matching", "host_matching", "expect_base", "expect_abc", "expect_xyz"),
[
(False, False, "default", "default", "default"),
(True, False, "default", "abc", "<invalid>"),
(False, True, "default", "abc", "default"),
],
)
def test_server_name_matching(
subdomain_matching: bool,
host_matching: bool,
expect_base: str,
expect_abc: str,
expect_xyz: str | int,
) -> None:
app = flask.Flask(
__name__,
subdomain_matching=subdomain_matching,
host_matching=host_matching,
static_host="example.test" if host_matching else None,
)
app.config["SERVER_NAME"] = "example.test"

@app.route("/", defaults={"name": "default"}, host="<name>")
@app.route("/", subdomain="<name>", host="<name>.example.test")
def index(name: str) -> str:
return name

client = app.test_client()

r = client.get(base_url="http://example.test")
assert r.text == expect_base

r = client.get(base_url="http://abc.example.test")
assert r.text == expect_abc

with pytest.warns() if subdomain_matching else nullcontext():
r = client.get(base_url="http://xyz.other.test")

if isinstance(expect_xyz, str):
assert r.text == expect_xyz
else:
assert r.status_code == expect_xyz


def test_server_name_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
client = app.test_client()
Expand Down
36 changes: 14 additions & 22 deletions tests/test_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,10 @@ def index():


def test_nesting_subdomains(app, client) -> None:
subdomain = "api"
app.subdomain_matching = True
app.config["SERVER_NAME"] = "example.test"
client.allow_subdomain_redirects = True

parent = flask.Blueprint("parent", __name__)
child = flask.Blueprint("child", __name__)

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

parent.register_blueprint(child)
app.register_blueprint(parent, subdomain=subdomain)

client.allow_subdomain_redirects = True

domain_name = "domain.tld"
app.config["SERVER_NAME"] = domain_name
response = client.get("/child/", base_url="http://api." + domain_name)
app.register_blueprint(parent, subdomain="api")

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


def test_child_and_parent_subdomain(app, client) -> None:
child_subdomain = "api"
parent_subdomain = "parent"
app.subdomain_matching = True
app.config["SERVER_NAME"] = "example.test"
client.allow_subdomain_redirects = True

parent = flask.Blueprint("parent", __name__)
child = flask.Blueprint("child", __name__, subdomain=child_subdomain)
child = flask.Blueprint("child", __name__, subdomain="api")

@child.route("/")
def index():
return "child"

parent.register_blueprint(child)
app.register_blueprint(parent, subdomain=parent_subdomain)

client.allow_subdomain_redirects = True

domain_name = "domain.tld"
app.config["SERVER_NAME"] = domain_name
response = client.get(
"/", base_url=f"http://{child_subdomain}.{parent_subdomain}.{domain_name}"
)
app.register_blueprint(parent, subdomain="parent")

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

response = client.get("/", base_url=f"http://{parent_subdomain}.{domain_name}")

response = client.get("/", base_url="http://parent.example.test")
assert response.status_code == 404


Expand Down

0 comments on commit b04dfc4

Please sign in to comment.