1
1
import base64
2
+ import binascii
3
+ import hmac
2
4
import time
3
5
from typing import Any
6
+ from urllib .parse import unquote
4
7
5
8
from starlette .requests import Request
6
9
@@ -58,7 +61,7 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
58
61
if not client :
59
62
raise AuthenticationError ("Invalid client_id" )
60
63
61
- request_client_secret = None
64
+ request_client_secret : str | None = None
62
65
auth_header = request .headers .get ("Authorization" , "" )
63
66
64
67
if client .token_endpoint_auth_method == "client_secret_basic" :
@@ -72,17 +75,20 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
72
75
raise ValueError ("Invalid Basic auth format" )
73
76
basic_client_id , request_client_secret = decoded .split (":" , 1 )
74
77
78
+ # URL-decode both parts per RFC 6749 Section 2.3.1
79
+ basic_client_id = unquote (basic_client_id )
80
+ request_client_secret = unquote (request_client_secret )
81
+
75
82
if basic_client_id != client_id :
76
83
raise AuthenticationError ("Client ID mismatch in Basic auth" )
77
- except AuthenticationError :
78
- raise
79
- except Exception :
84
+ except (ValueError , UnicodeDecodeError , binascii .Error ):
80
85
raise AuthenticationError ("Invalid Basic authentication header" )
81
86
82
87
elif client .token_endpoint_auth_method == "client_secret_post" :
83
- request_client_secret = form_data .get ("client_secret" )
84
- if request_client_secret :
85
- request_client_secret = str (request_client_secret )
88
+ raw_form_data = form_data .get ("client_secret" )
89
+ # form_data.get() can return a UploadFile or None, so we need to check if it's a string
90
+ if isinstance (raw_form_data , str ):
91
+ request_client_secret = str (raw_form_data )
86
92
87
93
elif client .token_endpoint_auth_method == "none" :
88
94
request_client_secret = None
@@ -93,7 +99,10 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
93
99
if not request_client_secret :
94
100
raise AuthenticationError ("Client secret is required" )
95
101
96
- if client .client_secret != request_client_secret :
102
+ # hmac.compare_digest requires that both arguments are either bytes or a `str` containing
103
+ # only ASCII characters. Since we do not control `request_client_secret`, we encode both
104
+ # arguments to bytes.
105
+ if not hmac .compare_digest (client .client_secret .encode (), request_client_secret .encode ()):
97
106
raise AuthenticationError ("Invalid client_secret" )
98
107
99
108
if client .client_secret_expires_at and client .client_secret_expires_at < int (time .time ()):
0 commit comments