Skip to content

Commit c1f237d

Browse files
authored
Merge pull request #728 from azmeuk/720-error-description
Fix invalid characters in 'error_description'
2 parents 80737a5 + 35e210b commit c1f237d

19 files changed

+86
-52
lines changed

authlib/jose/errors.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -59,46 +59,46 @@ class KeyMismatchError(JoseError):
5959

6060
class MissingEncryptionAlgorithmError(JoseError):
6161
error = "missing_encryption_algorithm"
62-
description = 'Missing "enc" in header'
62+
description = "Missing 'enc' in header"
6363

6464

6565
class UnsupportedEncryptionAlgorithmError(JoseError):
6666
error = "unsupported_encryption_algorithm"
67-
description = 'Unsupported "enc" value in header'
67+
description = "Unsupported 'enc' value in header"
6868

6969

7070
class UnsupportedCompressionAlgorithmError(JoseError):
7171
error = "unsupported_compression_algorithm"
72-
description = 'Unsupported "zip" value in header'
72+
description = "Unsupported 'zip' value in header"
7373

7474

7575
class InvalidUseError(JoseError):
7676
error = "invalid_use"
77-
description = 'Key "use" is not valid for your usage'
77+
description = "Key 'use' is not valid for your usage"
7878

7979

8080
class InvalidClaimError(JoseError):
8181
error = "invalid_claim"
8282

8383
def __init__(self, claim):
8484
self.claim_name = claim
85-
description = f'Invalid claim "{claim}"'
85+
description = f"Invalid claim '{claim}'"
8686
super().__init__(description=description)
8787

8888

8989
class MissingClaimError(JoseError):
9090
error = "missing_claim"
9191

9292
def __init__(self, claim):
93-
description = f'Missing "{claim}" claim'
93+
description = f"Missing '{claim}' claim"
9494
super().__init__(description=description)
9595

9696

9797
class InsecureClaimError(JoseError):
9898
error = "insecure_claim"
9999

100100
def __init__(self, claim):
101-
description = f'Insecure claim "{claim}"'
101+
description = f"Insecure claim '{claim}'"
102102
super().__init__(description=description)
103103

104104

authlib/oauth2/base.py

+29
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22
from authlib.common.urls import add_params_to_uri
33

44

5+
def invalid_error_characters(text: str) -> list[str]:
6+
"""Check whether the string only contains characters from the restricted ASCII set defined in RFC6749 for errors.
7+
8+
https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
9+
"""
10+
valid_ranges = [
11+
(0x20, 0x21),
12+
(0x23, 0x5B),
13+
(0x5D, 0x7E),
14+
]
15+
16+
return [
17+
char
18+
for char in set(text)
19+
if not any(start <= ord(char) <= end for start, end in valid_ranges)
20+
]
21+
22+
523
class OAuth2Error(AuthlibHTTPError):
624
def __init__(
725
self,
@@ -13,6 +31,17 @@ def __init__(
1331
redirect_fragment=False,
1432
error=None,
1533
):
34+
# Human-readable ASCII [USASCII] text providing
35+
# additional information, used to assist the client developer in
36+
# understanding the error that occurred.
37+
# Values for the "error_description" parameter MUST NOT include
38+
# characters outside the set %x20-21 / %x23-5B / %x5D-7E.
39+
if description:
40+
if chars := invalid_error_characters(description):
41+
raise ValueError(
42+
f"Error description contains forbidden characters: {', '.join(chars)}."
43+
)
44+
1645
super().__init__(error, description, uri, status_code)
1746
self.state = state
1847
self.redirect_uri = redirect_uri

authlib/oauth2/rfc6749/authorization_server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def create_endpoint_response(self, name, request=None):
264264
:return: Response
265265
"""
266266
if name not in self._endpoints:
267-
raise RuntimeError(f'There is no "{name}" endpoint.')
267+
raise RuntimeError(f"There is no '{name}' endpoint.")
268268

269269
endpoints = self._endpoints[name]
270270
for endpoint in endpoints:

authlib/oauth2/rfc6749/errors.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def get_headers(self):
217217

218218
class MissingAuthorizationError(ForbiddenError):
219219
error = "missing_authorization"
220-
description = 'Missing "Authorization" in headers.'
220+
description = "Missing 'Authorization' in headers."
221221

222222

223223
class UnsupportedTokenTypeError(ForbiddenError):
@@ -229,17 +229,17 @@ class UnsupportedTokenTypeError(ForbiddenError):
229229

230230
class MissingCodeException(OAuth2Error):
231231
error = "missing_code"
232-
description = 'Missing "code" in response.'
232+
description = "Missing 'code' in response."
233233

234234

235235
class MissingTokenException(OAuth2Error):
236236
error = "missing_token"
237-
description = 'Missing "access_token" in response.'
237+
description = "Missing 'access_token' in response."
238238

239239

240240
class MissingTokenTypeException(OAuth2Error):
241241
error = "missing_token_type"
242-
description = 'Missing "token_type" in response.'
242+
description = "Missing 'token_type' in response."
243243

244244

245245
class MismatchingStateException(OAuth2Error):

authlib/oauth2/rfc6749/grants/authorization_code.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -213,26 +213,26 @@ def validate_token_request(self):
213213
log.debug("Validate token request of %r", client)
214214
if not client.check_grant_type(self.GRANT_TYPE):
215215
raise UnauthorizedClientError(
216-
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"'
216+
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
217217
)
218218

219219
code = self.request.form.get("code")
220220
if code is None:
221-
raise InvalidRequestError('Missing "code" in request.')
221+
raise InvalidRequestError("Missing 'code' in request.")
222222

223223
# ensure that the authorization code was issued to the authenticated
224224
# confidential client, or if the client is public, ensure that the
225225
# code was issued to "client_id" in the request
226226
authorization_code = self.query_authorization_code(code, client)
227227
if not authorization_code:
228-
raise InvalidGrantError('Invalid "code" in request.')
228+
raise InvalidGrantError("Invalid 'code' in request.")
229229

230230
# validate redirect_uri parameter
231231
log.debug("Validate token redirect_uri of %r", client)
232232
redirect_uri = self.request.redirect_uri
233233
original_redirect_uri = authorization_code.get_redirect_uri()
234234
if original_redirect_uri and redirect_uri != original_redirect_uri:
235-
raise InvalidGrantError('Invalid "redirect_uri" in request.')
235+
raise InvalidGrantError("Invalid 'redirect_uri' in request.")
236236

237237
# save for create_token_response
238238
self.request.client = client
@@ -272,7 +272,7 @@ def create_token_response(self):
272272

273273
user = self.authenticate_user(authorization_code)
274274
if not user:
275-
raise InvalidGrantError('There is no "user" for this code.')
275+
raise InvalidGrantError("There is no 'user' for this code.")
276276
self.request.user = user
277277

278278
scope = authorization_code.get_scope()
@@ -373,7 +373,7 @@ def validate_code_authorization_request(grant):
373373
response_type = request.response_type
374374
if not client.check_response_type(response_type):
375375
raise UnauthorizedClientError(
376-
f'The client is not authorized to use "response_type={response_type}"',
376+
f"The client is not authorized to use 'response_type={response_type}'",
377377
state=grant.request.state,
378378
redirect_uri=redirect_uri,
379379
)

authlib/oauth2/rfc6749/grants/base.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def validate_authorization_redirect_uri(request: OAuth2Request, client):
141141
redirect_uri = client.get_default_redirect_uri()
142142
if not redirect_uri:
143143
raise InvalidRequestError(
144-
'Missing "redirect_uri" in request.', state=request.state
144+
"Missing 'redirect_uri' in request.", state=request.state
145145
)
146146
return redirect_uri
147147

@@ -157,7 +157,7 @@ def validate_no_multiple_request_parameter(request: OAuth2Request):
157157
for param in parameters:
158158
if len(datalist.get(param, [])) > 1:
159159
raise InvalidRequestError(
160-
f'Multiple "{param}" in request.', state=request.state
160+
f"Multiple '{param}' in request.", state=request.state
161161
)
162162

163163
def validate_consent_request(self):

authlib/oauth2/rfc6749/grants/client_credentials.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def validate_token_request(self):
6868

6969
if not client.check_grant_type(self.GRANT_TYPE):
7070
raise UnauthorizedClientError(
71-
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"'
71+
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
7272
)
7373

7474
self.request.client = client

authlib/oauth2/rfc6749/grants/implicit.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def validate_authorization_request(self):
130130
response_type = self.request.response_type
131131
if not client.check_response_type(response_type):
132132
raise UnauthorizedClientError(
133-
f'The client is not authorized to use "response_type={response_type}"',
133+
f"The client is not authorized to use 'response_type={response_type}'",
134134
state=self.request.state,
135135
redirect_uri=redirect_uri,
136136
redirect_fragment=True,

authlib/oauth2/rfc6749/grants/refresh_token.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ def _validate_request_client(self):
4141

4242
if not client.check_grant_type(self.GRANT_TYPE):
4343
raise UnauthorizedClientError(
44-
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"'
44+
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
4545
)
4646

4747
return client
4848

4949
def _validate_request_token(self, client):
5050
refresh_token = self.request.form.get("refresh_token")
5151
if refresh_token is None:
52-
raise InvalidRequestError('Missing "refresh_token" in request.')
52+
raise InvalidRequestError("Missing 'refresh_token' in request.")
5353

5454
token = self.authenticate_refresh_token(refresh_token)
5555
if not token or not token.check_client(client):
@@ -118,7 +118,7 @@ def create_token_response(self):
118118
refresh_token = self.request.refresh_token
119119
user = self.authenticate_user(refresh_token)
120120
if not user:
121-
raise InvalidRequestError('There is no "user" for this token.')
121+
raise InvalidRequestError("There is no 'user' for this token.")
122122

123123
client = self.request.client
124124
token = self.issue_token(user, refresh_token)

authlib/oauth2/rfc6749/grants/resource_owner_password_credentials.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,20 @@ def validate_token_request(self):
8989

9090
if not client.check_grant_type(self.GRANT_TYPE):
9191
raise UnauthorizedClientError(
92-
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"'
92+
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
9393
)
9494

9595
params = self.request.form
9696
if "username" not in params:
97-
raise InvalidRequestError('Missing "username" in request.')
97+
raise InvalidRequestError("Missing 'username' in request.")
9898
if "password" not in params:
99-
raise InvalidRequestError('Missing "password" in request.')
99+
raise InvalidRequestError("Missing 'password' in request.")
100100

101101
log.debug("Authenticate user of %r", params["username"])
102102
user = self.authenticate_user(params["username"], params["password"])
103103
if not user:
104104
raise InvalidRequestError(
105-
'Invalid "username" or "password" in request.',
105+
"Invalid 'username' or 'password' in request.",
106106
)
107107
self.request.client = client
108108
self.request.user = user

authlib/oauth2/rfc7523/assertion.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def sign_jwt_bearer_assertion(
2121
if alg:
2222
header["alg"] = alg
2323
if "alg" not in header:
24-
raise ValueError('Missing "alg" in header')
24+
raise ValueError("Missing 'alg' in header")
2525

2626
payload = {"iss": issuer, "aud": audience}
2727

authlib/oauth2/rfc7523/jwt_bearer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ def validate_token_request(self):
102102
"""
103103
assertion = self.request.form.get("assertion")
104104
if not assertion:
105-
raise InvalidRequestError('Missing "assertion" in request')
105+
raise InvalidRequestError("Missing 'assertion' in request")
106106

107107
claims = self.process_assertion_claims(assertion)
108108
client = self.resolve_issuer_client(claims["iss"])
109109
log.debug("Validate token request of %s", client)
110110

111111
if not client.check_grant_type(self.GRANT_TYPE):
112112
raise UnauthorizedClientError(
113-
f'The client is not authorized to use "grant_type={self.GRANT_TYPE}"'
113+
f"The client is not authorized to use 'grant_type={self.GRANT_TYPE}'"
114114
)
115115

116116
self.request.client = client
@@ -120,7 +120,7 @@ def validate_token_request(self):
120120
if subject:
121121
user = self.authenticate_user(subject)
122122
if not user:
123-
raise InvalidGrantError(description='Invalid "sub" value in assertion')
123+
raise InvalidGrantError(description="Invalid 'sub' value in assertion")
124124

125125
log.debug("Check client(%s) permission to User(%s)", client, user)
126126
if not self.has_granted_permission(client, user):

authlib/oauth2/rfc7636/challenge.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -74,27 +74,27 @@ def validate_code_challenge(self, grant):
7474
return
7575

7676
if not challenge:
77-
raise InvalidRequestError('Missing "code_challenge"')
77+
raise InvalidRequestError("Missing 'code_challenge'")
7878

7979
if len(request.datalist.get("code_challenge", [])) > 1:
80-
raise InvalidRequestError('Multiple "code_challenge" in request.')
80+
raise InvalidRequestError("Multiple 'code_challenge' in request.")
8181

8282
if not CODE_CHALLENGE_PATTERN.match(challenge):
83-
raise InvalidRequestError('Invalid "code_challenge"')
83+
raise InvalidRequestError("Invalid 'code_challenge'")
8484

8585
if method and method not in self.SUPPORTED_CODE_CHALLENGE_METHOD:
86-
raise InvalidRequestError('Unsupported "code_challenge_method"')
86+
raise InvalidRequestError("Unsupported 'code_challenge_method'")
8787

8888
if len(request.datalist.get("code_challenge_method", [])) > 1:
89-
raise InvalidRequestError('Multiple "code_challenge_method" in request.')
89+
raise InvalidRequestError("Multiple 'code_challenge_method' in request.")
9090

9191
def validate_code_verifier(self, grant):
9292
request: OAuth2Request = grant.request
9393
verifier = request.form.get("code_verifier")
9494

9595
# public client MUST verify code challenge
9696
if self.required and request.auth_method == "none" and not verifier:
97-
raise InvalidRequestError('Missing "code_verifier"')
97+
raise InvalidRequestError("Missing 'code_verifier'")
9898

9999
authorization_code = request.authorization_code
100100
challenge = self.get_authorization_code_challenge(authorization_code)
@@ -105,10 +105,10 @@ def validate_code_verifier(self, grant):
105105

106106
# challenge exists, code_verifier is required
107107
if not verifier:
108-
raise InvalidRequestError('Missing "code_verifier"')
108+
raise InvalidRequestError("Missing 'code_verifier'")
109109

110110
if not CODE_VERIFIER_PATTERN.match(verifier):
111-
raise InvalidRequestError('Invalid "code_verifier"')
111+
raise InvalidRequestError("Invalid 'code_verifier'")
112112

113113
# 4.6. Server Verifies code_verifier before Returning the Tokens
114114
method = self.get_authorization_code_challenge_method(authorization_code)
@@ -117,7 +117,7 @@ def validate_code_verifier(self, grant):
117117

118118
func = self.CODE_CHALLENGE_METHODS.get(method)
119119
if not func:
120-
raise RuntimeError(f'No verify method for "{method}"')
120+
raise RuntimeError(f"No verify method for '{method}'")
121121

122122
# If the values are not equal, an error response indicating
123123
# "invalid_grant" MUST be returned.

authlib/oauth2/rfc8628/device_code.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ def validate_token_request(self):
9191
"""
9292
device_code = self.request.data.get("device_code")
9393
if not device_code:
94-
raise InvalidRequestError('Missing "device_code" in payload')
94+
raise InvalidRequestError("Missing 'device_code' in payload")
9595

9696
client = self.authenticate_token_endpoint_client()
9797
if not client.check_grant_type(self.GRANT_TYPE):
9898
raise UnauthorizedClientError(
99-
f'The client is not authorized to use "response_type={self.GRANT_TYPE}"',
99+
f"The client is not authorized to use 'response_type={self.GRANT_TYPE}'",
100100
)
101101

102102
credential = self.query_device_credential(device_code)
103103
if not credential:
104-
raise InvalidRequestError('Invalid "device_code" in payload')
104+
raise InvalidRequestError("Invalid 'device_code' in payload")
105105

106106
if credential.get_client_id() != client.get_client_id():
107107
raise UnauthorizedClientError()

authlib/oidc/core/grants/hybrid.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def save_authorization_code(self, code, request):
5151
def validate_authorization_request(self):
5252
if not is_openid_scope(self.request.scope):
5353
raise InvalidScopeError(
54-
'Missing "openid" scope',
54+
"Missing 'openid' scope",
5555
redirect_uri=self.request.redirect_uri,
5656
redirect_fragment=True,
5757
)

0 commit comments

Comments
 (0)