Skip to content

Commit

Permalink
Merge 51bb0d0 into 78c4b74
Browse files Browse the repository at this point in the history
  • Loading branch information
amans330 committed May 29, 2021
2 parents 78c4b74 + 51bb0d0 commit 495f01e
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
15 changes: 15 additions & 0 deletions oauthlib/oauth2/rfc6749/clients/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ def __init__(self, client_id,
state=None,
redirect_url=None,
state_generator=generate_token,
code_verifier=None,
code_challenge=None,
code_challenge_method=None,
**kwargs):
"""Initialize a client with commonly used attributes.
Expand Down Expand Up @@ -99,6 +102,15 @@ def __init__(self, client_id,
:param state_generator: A no argument state generation callable. Defaults
to :py:meth:`oauthlib.common.generate_token`.
:param code_verifier: A cryptographically random string that is used to correlate the
authorization request to the token request.
:param code_challenge: A challenge derived from the code verifier that is sent in the
authorization request, to be verified against later.
:param code_challenge_method: A method that was used to derive code challenge.
Defaults to "plain" if not present in the request.
"""

self.client_id = client_id
Expand All @@ -113,6 +125,9 @@ def __init__(self, client_id,
self.state_generator = state_generator
self.state = state
self.redirect_url = redirect_url
self.code_verifier = code_verifier
self.code_challenge = code_challenge
self.code_challenge_method = code_challenge_method
self.code = None
self.expires_in = None
self._expires_at = None
Expand Down
24 changes: 20 additions & 4 deletions oauthlib/oauth2/rfc6749/clients/web_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, client_id, code=None, **kwargs):
self.code = code

def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
state=None, **kwargs):
state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
"""Prepare the authorization code request URI
The client constructs the request URI by adding the following
Expand All @@ -62,6 +62,12 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in `Section 10.12`_.
:param code_challenge: OPTIONAL. A challenge derived from the code verifier that is sent in the
authorization request, to be verified against later.
:param code_challenge_method: OPTIONAL. A method that was used to derive code challenge.
Defaults to "plain" if not present in the request.
:param kwargs: Extra arguments to include in the request URI.
In addition to supplied parameters, OAuthLib will append the ``client_id``
Expand All @@ -76,6 +82,10 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
>>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
>>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6')
'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6'
>>> client.prepare_request_uri('https://example.com', code_challenge_method='S256')
'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256'
>>> client.prepare_request_uri('https://example.com', foo='bar')
'https://example.com?client_id=your_id&response_type=code&foo=bar'
Expand All @@ -87,10 +97,11 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
"""
scope = self.scope if scope is None else scope
return prepare_grant_uri(uri, self.client_id, 'code',
redirect_uri=redirect_uri, scope=scope, state=state, **kwargs)
redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge,
code_challenge_method=code_challenge_method, **kwargs)

def prepare_request_body(self, code=None, redirect_uri=None, body='',
include_client_id=True, **kwargs):
include_client_id=True, code_verifier=None, **kwargs):
"""Prepare the access token request body.
The client makes a request to the token endpoint by adding the
Expand All @@ -113,6 +124,9 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='',
authorization server as described in `Section 3.2.1`_.
:type include_client_id: Boolean
:param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the
authorization request to the token request.
:param kwargs: Extra parameters to include in the token request.
In addition OAuthLib will add the ``grant_type`` parameter set to
Expand All @@ -127,6 +141,8 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='',
>>> client = WebApplicationClient('your_id')
>>> client.prepare_request_body(code='sh35ksdf09sf')
'grant_type=authorization_code&code=sh35ksdf09sf'
>>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR')
'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR'
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
Expand Down Expand Up @@ -154,7 +170,7 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='',
kwargs['client_id'] = self.client_id
kwargs['include_client_id'] = include_client_id
return prepare_token_request(self.grant_type, code=code, body=body,
redirect_uri=redirect_uri, **kwargs)
redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs)

def parse_request_uri_response(self, uri, state=None):
"""Parse the URI query for code and state.
Expand Down
19 changes: 17 additions & 2 deletions oauthlib/oauth2/rfc6749/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
scope=None, state=None, **kwargs):
scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
"""Prepare the authorization grant request URI.
The client constructs the request URI by adding the following
Expand All @@ -45,13 +45,18 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
back to the client. The parameter SHOULD be used for
preventing cross-site request forgery as described in
`Section 10.12`_.
:param code_challenge: A challenge derived from the code verifier that is sent in the
authorization request, to be verified against later.
:param code_challenge_method: A method that was used to derive code challenge.
Defaults to "plain" if not present in the request.
:param kwargs: Extra arguments to embed in the grant/authorization URL.
An example of an authorization code grant authorization URL:
.. code-block:: http
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
Expand All @@ -73,6 +78,9 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
params.append(('scope', list_to_scope(scope)))
if state:
params.append(('state', state))
if code_challenge is not None:
params.append(('code_challenge', code_challenge))
params.append(('code_challenge_method', code_challenge_method))

for k in kwargs:
if kwargs[k]:
Expand All @@ -81,7 +89,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
return add_params_to_uri(uri, params)


def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs):
def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs):
"""Prepare the access token request.
The client makes a request to the token endpoint by adding the
Expand Down Expand Up @@ -116,6 +124,9 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs)
authorization request as described in
`Section 4.1.1`_, and their values MUST be identical. *
:param code_verifier: A cryptographically random string that is used to correlate the
authorization request to the token request.
:param kwargs: Extra arguments to embed in the request body.
Parameters marked with a `*` above are not explicit arguments in the
Expand All @@ -142,6 +153,10 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs)
if client_id is not None:
params.append(('client_id', client_id))

# use code_verifier if code_challenge was passed in the authorization request
if code_verifier is not None:
params.append(('code_verifier', code_verifier))

# the kwargs iteration below only supports including boolean truth (truthy)
# values, but some servers may require an empty string for `client_secret`
client_secret = kwargs.pop('client_secret', None)
Expand Down
18 changes: 18 additions & 0 deletions tests/oauth2/rfc6749/clients/test_web_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ class WebApplicationClientTest(TestCase):
uri_id = uri + "&response_type=code&client_id=" + client_id
uri_redirect = uri_id + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback"
redirect_uri = "http://my.page.com/callback"
code_verifier = "code_verifier"
scope = ["/profile"]
state = "xyz"
code_challenge = "code_challenge"
code_challenge_method = "S256"
uri_scope = uri_id + "&scope=%2Fprofile"
uri_state = uri_id + "&state=" + state
uri_code_challenge = uri_id + "&code_challenge=" + code_challenge + "&code_challenge_method=" + code_challenge_method
uri_code_challenge_method = uri_id + "&code_challenge=" + code_challenge + "&code_challenge_method=plain"
kwargs = {
"some": "providers",
"require": "extra arguments"
Expand All @@ -40,6 +45,7 @@ class WebApplicationClientTest(TestCase):

body_code = "not=empty&grant_type=authorization_code&code={}&client_id={}".format(code, client_id)
body_redirect = body_code + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback"
bode_code_verifier = body_code + "&code_verifier=code_verifier"
body_kwargs = body_code + "&some=providers&require=extra+arguments"

response_uri = "https://client.example.com/cb?code=zzzzaaaa&state=xyz"
Expand Down Expand Up @@ -80,6 +86,14 @@ def test_auth_grant_uri(self):
uri = client.prepare_request_uri(self.uri, state=self.state)
self.assertURLEqual(uri, self.uri_state)

# with code_challenge and code_challenge_method
uri = client.prepare_request_uri(self.uri, code_challenge=self.code_challenge, code_challenge_method=self.code_challenge_method)
self.assertURLEqual(uri, self.uri_code_challenge)

# with no code_challenge_method
uri = client.prepare_request_uri(self.uri, code_challenge=self.code_challenge)
self.assertURLEqual(uri, self.uri_code_challenge_method)

# With extra parameters through kwargs
uri = client.prepare_request_uri(self.uri, **self.kwargs)
self.assertURLEqual(uri, self.uri_kwargs)
Expand All @@ -99,6 +113,10 @@ def test_request_body(self):
body = client.prepare_request_body(body=self.body, redirect_uri=self.redirect_uri)
self.assertFormBodyEqual(body, self.body_redirect)

# With code verifier
body = client.prepare_request_body(body=self.body, code_verifier=self.code_verifier)
self.assertFormBodyEqual(body, self.bode_code_verifier)

# With extra parameters
body = client.prepare_request_body(body=self.body, **self.kwargs)
self.assertFormBodyEqual(body, self.body_kwargs)
Expand Down
22 changes: 22 additions & 0 deletions tests/oauth2/rfc6749/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ class ParameterTests(TestCase):
list_scope = ['list', 'of', 'scopes']

auth_grant = {'response_type': 'code'}
auth_grant_pkce = {'response_type': 'code', 'code_challenge': "code_challenge",
'code_challenge_method': 'code_challenge_method'}
auth_grant_list_scope = {}
auth_implicit = {'response_type': 'token', 'extra': 'extra'}
auth_implicit_list_scope = {}

def setUp(self):
self.auth_grant.update(self.auth_base)
self.auth_grant_pkce.update(self.auth_base)
self.auth_implicit.update(self.auth_base)
self.auth_grant_list_scope.update(self.auth_grant)
self.auth_grant_list_scope['scope'] = self.list_scope
Expand All @@ -37,7 +40,14 @@ def setUp(self):
'&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2F'
'client.example.com%2Fcb&scope={1}&state={2}{3}')

auth_base_uri_pkce = ('https://server.example.com/authorize?response_type={0}'
'&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2F'
'client.example.com%2Fcb&scope={1}&state={2}{3}&code_challenge={4}'
'&code_challenge_method={5}')

auth_grant_uri = auth_base_uri.format('code', 'photos', state, '')
auth_grant_uri_pkce = auth_base_uri_pkce.format('code', 'photos', state, '', 'code_challenge',
'code_challenge_method')
auth_grant_uri_list_scope = auth_base_uri.format('code', 'list+of+scopes', state, '')
auth_implicit_uri = auth_base_uri.format('token', 'photos', state, '&extra=extra')
auth_implicit_uri_list_scope = auth_base_uri.format('token', 'list+of+scopes', state, '&extra=extra')
Expand All @@ -47,11 +57,21 @@ def setUp(self):
'code': 'SplxlOBeZQQYbYS6WxSbIA',
'redirect_uri': 'https://client.example.com/cb'
}
grant_body_pkce = {
'grant_type': 'authorization_code',
'code': 'SplxlOBeZQQYbYS6WxSbIA',
'redirect_uri': 'https://client.example.com/cb',
'code_verifier': 'code_verifier'
}
grant_body_scope = {'scope': 'photos'}
grant_body_list_scope = {'scope': list_scope}
auth_grant_body = ('grant_type=authorization_code&'
'code=SplxlOBeZQQYbYS6WxSbIA&'
'redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb')
auth_grant_body_pkce = ('grant_type=authorization_code&'
'code=SplxlOBeZQQYbYS6WxSbIA&'
'redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb'
'&code_verifier=code_verifier')
auth_grant_body_scope = auth_grant_body + '&scope=photos'
auth_grant_body_list_scope = auth_grant_body + '&scope=list+of+scopes'

Expand Down Expand Up @@ -179,12 +199,14 @@ def test_prepare_grant_uri(self):
self.assertURLEqual(prepare_grant_uri(**self.auth_grant_list_scope), self.auth_grant_uri_list_scope)
self.assertURLEqual(prepare_grant_uri(**self.auth_implicit), self.auth_implicit_uri)
self.assertURLEqual(prepare_grant_uri(**self.auth_implicit_list_scope), self.auth_implicit_uri_list_scope)
self.assertURLEqual(prepare_grant_uri(**self.auth_grant_pkce), self.auth_grant_uri_pkce)

def test_prepare_token_request(self):
"""Verify correct access token request body construction."""
self.assertFormBodyEqual(prepare_token_request(**self.grant_body), self.auth_grant_body)
self.assertFormBodyEqual(prepare_token_request(**self.pwd_body), self.password_body)
self.assertFormBodyEqual(prepare_token_request(**self.cred_grant), self.cred_body)
self.assertFormBodyEqual(prepare_token_request(**self.grant_body_pkce), self.auth_grant_body_pkce)

def test_grant_response(self):
"""Verify correct parameter parsing and validation for auth code responses."""
Expand Down

0 comments on commit 495f01e

Please sign in to comment.