Skip to content

Commit 687e76c

Browse files
jimfuqian/BB2-3525 Improve PKCE validation logic and error messages (#1311)
* improve PKCE validation logic and error messages. * remove 'gender' as required (subject to further cleanup). * temp comment gender stuff to pass integration tests * add tests * Re-add dispatch csrf exempt * revert the url in openapi.yaml (changed to localhost:8000 for testing things) * revert temporarily commented out integration tests code. --------- Co-authored-by: Logan Bertram <loganbertram@navapbc.com>
1 parent 7174bdf commit 687e76c

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

apps/pkce/oauth2_server.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import base64
22
import hashlib
33
from urllib.parse import urlparse
4-
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
4+
from oauthlib.oauth2.rfc6749.errors import OAuth2Error, InvalidRequestError
55

66
from oauth2_provider.models import get_grant_model
77
from oauth2_provider.settings import oauth2_settings
88
from django.core.exceptions import ObjectDoesNotExist
99

10+
ERR_CCM_S256_REQUIRED = "PKCE code_challenge_method required to be S256"
11+
ERR_CC_REQUIRED = "PKCE code_challenge required"
12+
ERR_CCM_REQUIRED = "code_challenge_method required for pkce, missing parameter: code_challenge_method=S256"
13+
1014
Grant = get_grant_model()
1115

1216

@@ -26,18 +30,26 @@ def validate_redirect_uri_pkce(request):
2630

2731
return {}
2832

29-
raise OAuth2Error("Non http uri scheme's must be used with pkce")
33+
raise InvalidRequestError("Non http uri scheme's must be used with pkce")
3034

3135
return {}
3236

3337

3438
def validate_code_challenge_method(request):
39+
# note: here the request is a sanitized oauthlib.Request object and always
40+
# has code_challenge_method, and code_challenge in its internal _params dict,
41+
# so it seems hasattr(request, 'code_challenge_method') always holds
3542
if hasattr(request, 'code_challenge_method') and request.code_challenge_method:
3643
if request.code_challenge_method != "S256":
37-
raise OAuth2Error("S256 code challenge method required for pkce")
44+
raise InvalidRequestError(ERR_CCM_S256_REQUIRED)
45+
# now code_challenge_method=S256 found, further check
46+
# code_challenge=<value> is present
47+
if not request.code_challenge:
48+
raise InvalidRequestError(ERR_CC_REQUIRED)
3849

3950
elif hasattr(request, 'code_challenge') and request.code_challenge:
40-
raise OAuth2Error("S256 code challenge method required for pkce")
51+
raise InvalidRequestError(ERR_CCM_REQUIRED)
52+
4153
return {}
4254

4355

apps/pkce/tests.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from django.test import TestCase
2+
from oauthlib.common import Request
3+
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
4+
5+
from apps.pkce.oauth2_server import validate_code_challenge_method, validate_redirect_uri_pkce
6+
from apps.pkce.oauth2_server import ERR_CCM_REQUIRED, ERR_CC_REQUIRED, ERR_CCM_S256_REQUIRED
7+
8+
PKCE_URIS = {
9+
"MISSING_CODE_CHALLENGE_METHOD_PARAM": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
10+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
11+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
12+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D&"
13+
"code_challenge=9GLy9RNcZrhuqk-kSSbqOHkkKTV8Og8PIbtkMPA7XjA%3D"),
14+
"CODE_CHALLENGE_METHOD_NOT_S256": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
15+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
16+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
17+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D&"
18+
"code_challenge=9GLy9RNcZrhuqk-kSSbqOHkkKTV8Og8PIbtkMPA7XjA%3D&"
19+
"code_challenge_method=plain"),
20+
"MISSING_CODE_CHALLENGE_PARAM": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
21+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
22+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
23+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D&code_challenge_method=S256"),
24+
"MISSING_CODE_CHALLENGE_VAL": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
25+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
26+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
27+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D&code_challenge=&"
28+
"code_challenge_method=S256"),
29+
"AUTH_URI_W_S256_AND_CHALLENGE_CODE": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
30+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
31+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
32+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D&"
33+
"code_challenge=9GLy9RNcZrhuqk-kSSbqOHkkKTV8Og8PIbtkMPA7XjA%3D&"
34+
"code_challenge_method=S256"),
35+
"AUTH_URI_NO_PKCE_PARAMS": ("/v2/o/authorize/b787ff17-e865-4893-aa79-4016ae5677a0/?"
36+
"response_type=code&client_id=XQFC5iseB84YGOidlqy0L9blhauz0pIffPnMi0qJ&"
37+
"redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftestclient%2Fcallback&"
38+
"state=QCh2XTdgS3RdfEI1Qz0uOll8KStOdShwfVlzWyNBTGA%3D"), }
39+
40+
41+
class TestOAuth2PKCEValidation(TestCase):
42+
def test_redirect_validation(self):
43+
r = validate_redirect_uri_pkce(Request(uri=PKCE_URIS['AUTH_URI_W_S256_AND_CHALLENGE_CODE']))
44+
self.assertFalse(r)
45+
46+
def test_non_pkce_validation(self):
47+
r = validate_code_challenge_method(Request(uri=PKCE_URIS['AUTH_URI_NO_PKCE_PARAMS']))
48+
self.assertFalse(r)
49+
50+
def test_pkce_validation(self):
51+
r = validate_code_challenge_method(Request(uri=PKCE_URIS["AUTH_URI_W_S256_AND_CHALLENGE_CODE"]))
52+
self.assertFalse(r)
53+
54+
def test_pkce_validation_no_ccm(self):
55+
with self.assertRaisesMessage(InvalidRequestError, ERR_CCM_REQUIRED):
56+
validate_code_challenge_method(Request(uri=PKCE_URIS["MISSING_CODE_CHALLENGE_METHOD_PARAM"]))
57+
58+
def test_pkce_validation_ccm_not_s256(self):
59+
with self.assertRaisesMessage(InvalidRequestError, ERR_CCM_S256_REQUIRED):
60+
validate_code_challenge_method(Request(uri=PKCE_URIS["CODE_CHALLENGE_METHOD_NOT_S256"]))
61+
62+
def test_pkce_validation_no_cc(self):
63+
with self.assertRaisesMessage(InvalidRequestError, ERR_CC_REQUIRED):
64+
validate_code_challenge_method(Request(uri=PKCE_URIS["MISSING_CODE_CHALLENGE_PARAM"]))
65+
66+
def test_pkce_validation_cc_no_val(self):
67+
with self.assertRaisesMessage(InvalidRequestError, ERR_CC_REQUIRED):
68+
validate_code_challenge_method(Request(uri=PKCE_URIS["MISSING_CODE_CHALLENGE_VAL"]))

0 commit comments

Comments
 (0)