API - add missing tests on Authorization Code Grant (code challenge)

This commit is contained in:
Sam 2022-06-19 18:47:42 +02:00
parent 40a5fcccf3
commit 6b497bd72f
2 changed files with 138 additions and 4 deletions

View File

@ -106,7 +106,10 @@ class ApiTestCaseMixin(OAuth2Mixin, RandomMixin):
oauth_client: OAuth2Client, oauth_client: OAuth2Client,
auth_token: str, auth_token: str,
scope: Optional[str] = None, scope: Optional[str] = None,
code_challenge: Optional[Dict] = None,
) -> Union[List[str], str]: ) -> Union[List[str], str]:
if code_challenge is None:
code_challenge = {}
response = client.post( response = client.post(
'/api/oauth/authorize', '/api/oauth/authorize',
data={ data={
@ -114,6 +117,7 @@ class ApiTestCaseMixin(OAuth2Mixin, RandomMixin):
'confirm': True, 'confirm': True,
'response_type': 'code', 'response_type': 'code',
'scope': 'read' if not scope else scope, 'scope': 'read' if not scope else scope,
**code_challenge,
}, },
headers=dict( headers=dict(
Authorization=f'Bearer {auth_token}', Authorization=f'Bearer {auth_token}',
@ -234,11 +238,14 @@ class ApiTestCaseMixin(OAuth2Mixin, RandomMixin):
) )
@staticmethod @staticmethod
def assert_invalid_request(response: TestResponse) -> Dict: def assert_invalid_request(
response: TestResponse, error_description: Optional[str] = None
) -> Dict:
return assert_oauth_errored_response( return assert_oauth_errored_response(
response, response,
400, 400,
error='invalid_request', error='invalid_request',
error_description=error_description,
) )
@staticmethod @staticmethod

View File

@ -1,8 +1,10 @@
import json import json
from typing import Dict, List, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from authlib.common.security import generate_token
from authlib.oauth2.rfc7636 import create_s256_code_challenge
from flask import Flask from flask import Flask
from werkzeug.test import TestResponse from werkzeug.test import TestResponse
@ -367,15 +369,82 @@ class TestOAuthClientAuthorization(ApiTestCaseMixin):
) )
class TestOAuthClientAuthorizationWithCodeChallenge(ApiTestCaseMixin):
route = '/api/oauth/authorize'
def test_it_returns_error_when_code_challenge_method_is_invalid(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
code_verifier = generate_token(48)
code_challenge = create_s256_code_challenge(code_verifier)
response = client.post(
self.route,
data={
'client_id': oauth_client.client_id,
'response_type': 'code',
'confirm': 'true',
'code_challenge': code_challenge,
'code_challenge_method': self.random_string(),
},
headers=dict(
Authorization=f'Bearer {auth_token}',
content_type='multipart/form-data',
),
)
self.assert_400(response, 'Unsupported "code_challenge_method"')
def test_it_creates_authorization_code(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
oauth_client = self.create_oauth_client(user_1)
code_verifier = generate_token(48)
code_challenge = create_s256_code_challenge(code_verifier)
response = client.post(
self.route,
data={
'client_id': oauth_client.client_id,
'response_type': 'code',
'confirm': 'true',
'code_challenge': code_challenge,
'code_challenge_method': 'S256',
},
headers=dict(
Authorization=f'Bearer {auth_token}',
content_type='multipart/form-data',
),
)
code = OAuth2AuthorizationCode.query.filter_by(
client_id=oauth_client.client_id
).first()
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['redirect_url'] == (
f'{oauth_client.get_default_redirect_uri()}?code={code.code}'
)
class OAuthIssueTokenTestCase(ApiTestCaseMixin): class OAuthIssueTokenTestCase(ApiTestCaseMixin):
def create_authorized_oauth_client( def create_authorized_oauth_client(
self, app: Flask, user_1: User self, app: Flask, user_1: User, code_challenge: Optional[Dict] = None
) -> Tuple[OAuth2Client, Union[List[str], str]]: ) -> Tuple[OAuth2Client, Union[List[str], str]]:
client, auth_token = self.get_test_client_and_auth_token( client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email app, user_1.email
) )
oauth_client = self.create_oauth_client(user_1) oauth_client = self.create_oauth_client(user_1)
code = self.authorize_client(client, oauth_client, auth_token) code = self.authorize_client(
client, oauth_client, auth_token, code_challenge=code_challenge
)
return oauth_client, code return oauth_client, code
@staticmethod @staticmethod
@ -497,6 +566,64 @@ class TestOAuthIssueAccessToken(OAuthIssueTokenTestCase):
self.assert_token_is_returned(response) self.assert_token_is_returned(response)
class TestOAuthIssueAccessTokenWithCodeChallenge(OAuthIssueTokenTestCase):
route = '/api/oauth/token'
def test_it_raises_error_when_code_verifier_is_invalid(
self, app: Flask, user_1: User
) -> None:
code_verifier = generate_token(48)
code_challenge = create_s256_code_challenge(code_verifier)
oauth_client, code = self.create_authorized_oauth_client(
app,
user_1,
{
'code_challenge': code_challenge,
'code_challenge_method': 'S256',
},
)
client = app.test_client()
response = client.post(
self.route,
data={
'client_id': oauth_client.client_id,
'client_secret': oauth_client.client_secret,
'grant_type': 'authorization_code',
'code': code,
'code_verifier': self.random_string(),
},
headers=dict(content_type='multipart/form-data'),
)
self.assert_invalid_request(response, 'Invalid "code_verifier"')
def test_it_returns_access_token(self, app: Flask, user_1: User) -> None:
code_verifier = generate_token(48)
oauth_client, code = self.create_authorized_oauth_client(
app,
user_1,
code_challenge={
'code_challenge': create_s256_code_challenge(code_verifier),
'code_challenge_method': 'S256',
},
)
client = app.test_client()
response = client.post(
self.route,
data={
'client_id': oauth_client.client_id,
'client_secret': oauth_client.client_secret,
'grant_type': 'authorization_code',
'code': code,
'code_verifier': code_verifier,
},
headers=dict(content_type='multipart/form-data'),
)
self.assert_token_is_returned(response)
class TestOAuthIssueRefreshToken(OAuthIssueTokenTestCase): class TestOAuthIssueRefreshToken(OAuthIssueTokenTestCase):
route = '/api/oauth/token' route = '/api/oauth/token'