Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/websockets/asyncio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ def check_credentials(username: str, password: str) -> bool:
expected_password = credentials_dict[username]
except KeyError:
return False
return hmac.compare_digest(expected_password, password)
return hmac.compare_digest(expected_password.encode(), password.encode())

assert check_credentials is not None # help mypy

Expand Down
2 changes: 1 addition & 1 deletion src/websockets/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ def parse_authorization_basic(header: str) -> tuple[str, str]:

try:
user_pass = base64.b64decode(basic_credentials.encode()).decode()
except binascii.Error:
except (binascii.Error, UnicodeDecodeError):
raise InvalidHeaderValue(
"Authorization",
"expected base64-encoded credentials",
Expand Down
2 changes: 1 addition & 1 deletion src/websockets/legacy/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ async def check_credentials(username: str, password: str) -> bool:
expected_password = credentials_dict[username]
except KeyError:
return False
return hmac.compare_digest(expected_password, password)
return hmac.compare_digest(expected_password.encode(), password.encode())

if create_protocol is None:
create_protocol = BasicAuthWebSocketServerProtocol
Expand Down
2 changes: 1 addition & 1 deletion src/websockets/sync/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ def check_credentials(username: str, password: str) -> bool:
expected_password = credentials_dict[username]
except KeyError:
return False
return hmac.compare_digest(expected_password, password)
return hmac.compare_digest(expected_password.encode(), password.encode())

assert check_credentials is not None # help mypy

Expand Down
29 changes: 29 additions & 0 deletions tests/asyncio/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,35 @@ async def test_authorization_with_incorrect_password(self):
"server rejected WebSocket connection: HTTP 401",
)

async def test_authorization_with_non_ascii_password(self):
"""basic_auth authenticates client with a non-ASCII password."""
async with serve(
*args,
process_request=basic_auth(credentials=("test", "123£")),
) as server:
async with connect(
get_uri(server),
additional_headers={"Authorization": "Basic dGVzdDoxMjPCow=="},
) as client:
await self.assertEval(client, "ws.username", "test")

async def test_authorization_with_incorrect_non_ascii_password(self):
"""basic_auth rejects client with incorrect non-ASCII password."""
async with serve(
*args,
process_request=basic_auth(credentials=("test", "iloveyou")),
) as server:
with self.assertRaises(InvalidStatus) as raised:
async with connect(
get_uri(server),
additional_headers={"Authorization": "Basic dGVzdDoxMjPCow=="},
):
self.fail("did not raise")
self.assertEqual(
str(raised.exception),
"server rejected WebSocket connection: HTTP 401",
)

async def test_list_of_credentials(self):
"""basic_auth accepts a list of hard coded credentials."""
async with serve(
Expand Down
27 changes: 27 additions & 0 deletions tests/sync/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,33 @@ def test_authorization_with_incorrect_password(self):
"server rejected WebSocket connection: HTTP 401",
)

def test_authorization_with_non_ascii_password(self):
"""basic_auth authenticates client with a non-ASCII password."""
with run_server(
process_request=basic_auth(credentials=("test", "123£")),
) as server:
with connect(
get_uri(server),
additional_headers={"Authorization": "Basic dGVzdDoxMjPCow=="},
) as client:
self.assertEval(client, "ws.username", "test")

def test_authorization_with_incorrect_non_ascii_password(self):
"""basic_auth rejects client with incorrect non-ASCII password."""
with run_server(
process_request=basic_auth(credentials=("test", "iloveyou")),
) as server:
with self.assertRaises(InvalidStatus) as raised:
with connect(
get_uri(server),
additional_headers={"Authorization": "Basic dGVzdDoxMjPCow=="},
):
self.fail("did not raise")
self.assertEqual(
str(raised.exception),
"server rejected WebSocket connection: HTTP 401",
)

def test_list_of_credentials(self):
"""basic_auth accepts a list of hard coded credentials."""
with run_server(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ def test_parse_authorization_basic_invalid_header_value(self):
"Digest ...",
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ",
"Basic QWxhZGNlc2FtZQ==",
# Credentials aren't valid UTF-8
"Basic dGVzdDr/",
]:
with self.subTest(header=header):
with self.assertRaises(InvalidHeaderValue):
Expand Down