More secure URL matching for auth tokens

This commit is contained in:
Simon Willison 2023-07-17 15:49:30 -07:00
commit 7807b8363f
3 changed files with 73 additions and 1 deletions

View file

@ -2,6 +2,7 @@ import click
import httpx
import json
import pathlib
from .utils import url_matches_prefix
def get_config_dir():
@ -79,7 +80,7 @@ def query(url, sql, token):
def _token_for_url_from_auth(url, auth_file):
auths = _load_auths(auth_file)
for auth_url, token in auths.items():
if url.startswith(auth_url):
if url_matches_prefix(url, auth_url):
return token

30
dclient/utils.py Normal file
View file

@ -0,0 +1,30 @@
from urllib.parse import urlparse
def url_matches_prefix(url: str, prefix: str) -> bool:
url_parsed = urlparse(url)
prefix_parsed = urlparse(prefix)
if (
url_parsed.scheme != prefix_parsed.scheme
or url_parsed.netloc != prefix_parsed.netloc
):
return False
if url_parsed.path == prefix_parsed.path:
return True
url_path = url_parsed.path
# Add '/' to the end of the paths if they don't already end with '/'
prefix_path = prefix_parsed.path
# Special treatment for /foo.json
if url_path.endswith(".json"):
if prefix_path + ".json" == url_path:
return True
if not prefix_path.endswith("/"):
prefix_path += "/"
return url_path.startswith(prefix_path)

41
tests/test_utils.py Normal file
View file

@ -0,0 +1,41 @@
from dclient.utils import url_matches_prefix
import pytest
@pytest.mark.parametrize(
"url,prefix_url,expected",
(
("https://example.com/foo/bar", "https://example.com/foo", True),
("https://example.com/foo/bar2", "https://example.com/foo/bar", False),
("https://example.com/foo/bar/baz", "https://example.com/foo/bar", True),
("https://example.com/foo/bar/baz", "https://example.com/foo", True),
("https://example.com/foo.json", "https://example.com/foo", True),
# different scheme
(
"http://example.com/foo/bar",
"https://example.com/foo",
False,
),
# different netloc
(
"https://example.org/foo/bar",
"https://example.com/foo",
False,
),
# exactly the same
("https://example.com/foo", "https://example.com/foo", True),
# trailing '/'
(
"https://example.com/foo/bar",
"https://example.com/foo/bar/",
False,
),
(
"https://example.com/foo/bar/baz",
"https://example.com/foo/bar/",
True,
),
),
)
def test_url_matches_prefix(url, prefix_url, expected):
assert url_matches_prefix(url, prefix_url) == expected