mirror of https://github.com/pallets/flask.git
no cookie domain by default
This commit is contained in:
parent
fa0ceb62f2
commit
c24f8c8199
|
@ -37,6 +37,10 @@ Unreleased
|
||||||
binary file instead. :issue:`4989`
|
binary file instead. :issue:`4989`
|
||||||
- If a blueprint is created with an empty name it raises a ``ValueError``.
|
- If a blueprint is created with an empty name it raises a ``ValueError``.
|
||||||
:issue:`5010`
|
:issue:`5010`
|
||||||
|
- ``SESSION_COOKIE_DOMAIN`` does not fall back to ``SERVER_NAME``. The default is not
|
||||||
|
to set the domain, which modern browsers interpret as an exact match rather than
|
||||||
|
a subdomain match. Warnings about ``localhost`` and IP addresses are also removed.
|
||||||
|
:issue:`5051`
|
||||||
|
|
||||||
|
|
||||||
Version 2.2.4
|
Version 2.2.4
|
||||||
|
|
|
@ -134,12 +134,17 @@ 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 value of the ``Domain`` parameter on the session cookie. If not set, browsers
|
||||||
set, the cookie will be valid for all subdomains of :data:`SERVER_NAME`.
|
will only send the cookie to the exact domain it was set from. Otherwise, they
|
||||||
If ``False``, the cookie's domain will not be set.
|
will send it to any subdomain of the given value as well.
|
||||||
|
|
||||||
|
Not setting this value is more restricted and secure than setting it.
|
||||||
|
|
||||||
Default: ``None``
|
Default: ``None``
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Not set by default, does not fall back to ``SERVER_NAME``.
|
||||||
|
|
||||||
.. py:data:: SESSION_COOKIE_PATH
|
.. py:data:: SESSION_COOKIE_PATH
|
||||||
|
|
||||||
The path that the session cookie will be valid for. If not set, the cookie
|
The path that the session cookie will be valid for. If not set, the cookie
|
||||||
|
@ -219,19 +224,14 @@ The following configuration values are used internally by Flask:
|
||||||
Inform the application what host and port it is bound to. Required
|
Inform the application what host and port it is bound to. Required
|
||||||
for subdomain route matching support.
|
for subdomain route matching support.
|
||||||
|
|
||||||
If set, will be used for the session cookie domain if
|
|
||||||
:data:`SESSION_COOKIE_DOMAIN` is not set. Modern web browsers will
|
|
||||||
not allow setting cookies for domains without a dot. To use a domain
|
|
||||||
locally, add any names that should route to the app to your
|
|
||||||
``hosts`` file. ::
|
|
||||||
|
|
||||||
127.0.0.1 localhost.dev
|
|
||||||
|
|
||||||
If set, ``url_for`` can generate external URLs with only an application
|
If set, ``url_for`` can generate external URLs with only an application
|
||||||
context instead of a request context.
|
context instead of a request context.
|
||||||
|
|
||||||
Default: ``None``
|
Default: ``None``
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Does not affect ``SESSION_COOKIE_DOMAIN``.
|
||||||
|
|
||||||
.. py:data:: APPLICATION_ROOT
|
.. py:data:: APPLICATION_ROOT
|
||||||
|
|
||||||
Inform the application what path it is mounted under by the application /
|
Inform the application what path it is mounted under by the application /
|
||||||
|
|
|
@ -3,6 +3,7 @@ import pkgutil
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
|
import warnings
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
@ -662,7 +663,16 @@ def is_ip(value: str) -> bool:
|
||||||
|
|
||||||
:return: True if string is an IP address
|
:return: True if string is an IP address
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
|
.. deprecated:: 2.3
|
||||||
|
Will be removed in Flask 2.4.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"The 'is_ip' function is deprecated and will be removed in Flask 2.4.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
for family in (socket.AF_INET, socket.AF_INET6):
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
try:
|
try:
|
||||||
socket.inet_pton(family, value)
|
socket.inet_pton(family, value)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import typing as t
|
import typing as t
|
||||||
import warnings
|
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
@ -9,7 +8,6 @@ from itsdangerous import BadSignature
|
||||||
from itsdangerous import URLSafeTimedSerializer
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
from werkzeug.datastructures import CallbackDict
|
from werkzeug.datastructures import CallbackDict
|
||||||
|
|
||||||
from .helpers import is_ip
|
|
||||||
from .json.tag import TaggedJSONSerializer
|
from .json.tag import TaggedJSONSerializer
|
||||||
|
|
||||||
if t.TYPE_CHECKING: # pragma: no cover
|
if t.TYPE_CHECKING: # pragma: no cover
|
||||||
|
@ -181,62 +179,17 @@ class SessionInterface:
|
||||||
return app.config["SESSION_COOKIE_NAME"]
|
return app.config["SESSION_COOKIE_NAME"]
|
||||||
|
|
||||||
def get_cookie_domain(self, app: "Flask") -> t.Optional[str]:
|
def get_cookie_domain(self, app: "Flask") -> t.Optional[str]:
|
||||||
"""Returns the domain that should be set for the session cookie.
|
"""The value of the ``Domain`` parameter on the session cookie. If not set,
|
||||||
|
browsers will only send the cookie to the exact domain it was set from.
|
||||||
|
Otherwise, they will send it to any subdomain of the given value as well.
|
||||||
|
|
||||||
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
Uses the :data:`SESSION_COOKIE_DOMAIN` config.
|
||||||
falls back to detecting the domain based on ``SERVER_NAME``.
|
|
||||||
|
|
||||||
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
.. versionchanged:: 2.3
|
||||||
updated to avoid re-running the logic.
|
Not set by default, does not fall back to ``SERVER_NAME``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rv = app.config["SESSION_COOKIE_DOMAIN"]
|
rv = app.config["SESSION_COOKIE_DOMAIN"]
|
||||||
|
return rv if rv else None
|
||||||
# set explicitly, or cached from SERVER_NAME detection
|
|
||||||
# if False, return None
|
|
||||||
if rv is not None:
|
|
||||||
return rv if rv else None
|
|
||||||
|
|
||||||
rv = app.config["SERVER_NAME"]
|
|
||||||
|
|
||||||
# server name not set, cache False to return none next time
|
|
||||||
if not rv:
|
|
||||||
app.config["SESSION_COOKIE_DOMAIN"] = False
|
|
||||||
return None
|
|
||||||
|
|
||||||
# chop off the port which is usually not supported by browsers
|
|
||||||
# remove any leading '.' since we'll add that later
|
|
||||||
rv = rv.rsplit(":", 1)[0].lstrip(".")
|
|
||||||
|
|
||||||
if "." not in rv:
|
|
||||||
# Chrome doesn't allow names without a '.'. This should only
|
|
||||||
# come up with localhost. Hack around this by not setting
|
|
||||||
# the name, and show a warning.
|
|
||||||
warnings.warn(
|
|
||||||
f"{rv!r} is not a valid cookie domain, it must contain"
|
|
||||||
" a '.'. Add an entry to your hosts file, for example"
|
|
||||||
f" '{rv}.localdomain', and use that instead."
|
|
||||||
)
|
|
||||||
app.config["SESSION_COOKIE_DOMAIN"] = False
|
|
||||||
return None
|
|
||||||
|
|
||||||
ip = is_ip(rv)
|
|
||||||
|
|
||||||
if ip:
|
|
||||||
warnings.warn(
|
|
||||||
"The session cookie domain is an IP address. This may not work"
|
|
||||||
" as intended in some browsers. Add an entry to your hosts"
|
|
||||||
' file, for example "localhost.localdomain", and use that'
|
|
||||||
" instead."
|
|
||||||
)
|
|
||||||
|
|
||||||
# if this is not an ip and app is mounted at the root, allow subdomain
|
|
||||||
# matching by adding a '.' prefix
|
|
||||||
if self.get_cookie_path(app) == "/" and not ip:
|
|
||||||
rv = f".{rv}"
|
|
||||||
|
|
||||||
app.config["SESSION_COOKIE_DOMAIN"] = rv
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def get_cookie_path(self, app: "Flask") -> str:
|
def get_cookie_path(self, app: "Flask") -> str:
|
||||||
"""Returns the path for which the cookie should be valid. The
|
"""Returns the path for which the cookie should be valid. The
|
||||||
|
|
|
@ -251,36 +251,8 @@ def test_session(app, client):
|
||||||
assert client.get("/get").data == b"42"
|
assert client.get("/get").data == b"42"
|
||||||
|
|
||||||
|
|
||||||
def test_session_using_server_name(app, client):
|
def test_session_path(app, client):
|
||||||
app.config.update(SERVER_NAME="example.com")
|
app.config.update(APPLICATION_ROOT="/foo")
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
flask.session["testing"] = 42
|
|
||||||
return "Hello World"
|
|
||||||
|
|
||||||
rv = client.get("/", "http://example.com/")
|
|
||||||
cookie = rv.headers["set-cookie"].lower()
|
|
||||||
# or condition for Werkzeug < 2.3
|
|
||||||
assert "domain=example.com" in cookie or "domain=.example.com" in cookie
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_using_server_name_and_port(app, client):
|
|
||||||
app.config.update(SERVER_NAME="example.com:8080")
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
flask.session["testing"] = 42
|
|
||||||
return "Hello World"
|
|
||||||
|
|
||||||
rv = client.get("/", "http://example.com:8080/")
|
|
||||||
cookie = rv.headers["set-cookie"].lower()
|
|
||||||
# or condition for Werkzeug < 2.3
|
|
||||||
assert "domain=example.com" in cookie or "domain=.example.com" in cookie
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_using_server_name_port_and_path(app, client):
|
|
||||||
app.config.update(SERVER_NAME="example.com:8080", APPLICATION_ROOT="/foo")
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
|
@ -288,9 +260,7 @@ def test_session_using_server_name_port_and_path(app, client):
|
||||||
return "Hello World"
|
return "Hello World"
|
||||||
|
|
||||||
rv = client.get("/", "http://example.com:8080/foo")
|
rv = client.get("/", "http://example.com:8080/foo")
|
||||||
assert "domain=example.com" in rv.headers["set-cookie"].lower()
|
|
||||||
assert "path=/foo" in rv.headers["set-cookie"].lower()
|
assert "path=/foo" in rv.headers["set-cookie"].lower()
|
||||||
assert "httponly" in rv.headers["set-cookie"].lower()
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_using_application_root(app, client):
|
def test_session_using_application_root(app, client):
|
||||||
|
@ -382,34 +352,6 @@ def test_session_using_samesite_attribute(app, client):
|
||||||
assert "samesite=lax" in cookie
|
assert "samesite=lax" in cookie
|
||||||
|
|
||||||
|
|
||||||
def test_session_localhost_warning(recwarn, app, client):
|
|
||||||
app.config.update(SERVER_NAME="localhost:5000")
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
flask.session["testing"] = 42
|
|
||||||
return "testing"
|
|
||||||
|
|
||||||
rv = client.get("/", "http://localhost:5000/")
|
|
||||||
assert "domain" not in rv.headers["set-cookie"].lower()
|
|
||||||
w = recwarn.pop(UserWarning)
|
|
||||||
assert "'localhost' is not a valid cookie domain" in str(w.message)
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_ip_warning(recwarn, app, client):
|
|
||||||
app.config.update(SERVER_NAME="127.0.0.1:5000")
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
flask.session["testing"] = 42
|
|
||||||
return "testing"
|
|
||||||
|
|
||||||
rv = client.get("/", "http://127.0.0.1:5000/")
|
|
||||||
assert "domain=127.0.0.1" in rv.headers["set-cookie"].lower()
|
|
||||||
w = recwarn.pop(UserWarning)
|
|
||||||
assert "cookie domain is an IP" in str(w.message)
|
|
||||||
|
|
||||||
|
|
||||||
def test_missing_session(app):
|
def test_missing_session(app):
|
||||||
app.secret_key = None
|
app.secret_key = None
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue