Skip to content

Commit aca4388

Browse files
BB2-4697 fail in the authorize endpoint if app is only allowed client credentials (#1568)
Make it so that a V3 authorize request will fail early (before user enters medicare.gov credentials) if the app can only use client credentials flow. Also, update name of test class to reflect behavior. * fail in the authorize endpoint if app is only allowed client credentials * also apply client credentials check to non-v3 calls * rename test class TestAuthorizeWithCustomScheme This class seems to contain tests that are not just about custom schemes, so renaming to reflect that * update signature of validate_app_is_active to reflect behavior * ensure application exists before checking allowed_auth_type since the app can be none * first draft of a test for authorize call for an app that only has client credentials * cleanup test * WIP on parametrizing test post method on v3 doesn't work yet for some reason * Revert "update signature of validate_app_is_active to reflect behavior" Done because the behavior was changed in #1575 and now the existing signature is correct. This reverts commit 2b779e8. * remove check that is no longer necessary self.application will have a value by this point, or an error will have been raised * resolve todo comments and move into review notes --------- Co-authored-by: James Demery <jamesdemery@navapbc.com>
1 parent 3c7db49 commit aca4388

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

apps/dot_ext/tests/test_authorization.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from urllib.parse import parse_qs, urlencode, urlparse
1616
import uuid
1717
from waffle.testutils import override_switch
18+
from apps.dot_ext.constants import APPLICATION_HAS_CLIENT_CREDENTIALS_ENABLED_NON_CLIENT_CREDENTIALS_AUTH_CALL_MADE, CLIENT_CREDENTIALS_TYPE
1819
from apps.fhir.bluebutton.models import Crosswalk
1920
from apps.constants import CODE_CHALLENGE_METHOD_S256
2021
from apps.authorization.models import DataAccessGrant, ArchivedDataAccessGrant
@@ -30,7 +31,7 @@
3031
RefreshToken = get_refresh_token_model()
3132

3233

33-
class TestAuthorizeWithCustomScheme(BaseApiTest):
34+
class TestAuthorizationView(BaseApiTest):
3435
def _create_authorization_header(self, client_id, client_secret):
3536
return 'Basic {0}'.format(base64.b64encode('{0}:{1}'.format(client_id, client_secret).encode('utf-8')).decode('utf-8'))
3637

@@ -1575,3 +1576,70 @@ def test_authorization_endpoint_across_versions_and_methods(self):
15751576

15761577
self.assertEqual(response.status_code, HTTPStatus.FOUND)
15771578
self.assertTrue(response.url.startswith('/mymedicare/login'))
1579+
1580+
@override_switch('v3_endpoints', active=True)
1581+
def test_fail_when_app_only_allowed_client_credentials(self):
1582+
"""
1583+
The authorization view should fail when an app is only allowed to use the
1584+
client credentials flow.
1585+
1586+
This means that a user going through an authorization code flow when the
1587+
app is not allowed it will get an error before being shown the medicare.gov login page.
1588+
"""
1589+
redirect_uri = 'com.custom.bluebutton://example.it'
1590+
self._create_user('anna', '123456')
1591+
capability_a = self._create_capability('Capability A', [])
1592+
application = self._create_application(
1593+
'an app',
1594+
grant_type=Application.GRANT_AUTHORIZATION_CODE,
1595+
client_type=Application.CLIENT_CONFIDENTIAL,
1596+
redirect_uris=redirect_uri,
1597+
)
1598+
application.allowed_auth_type = CLIENT_CREDENTIALS_TYPE
1599+
application.jwks_uri = 'https://example.it'
1600+
application.scope.add(capability_a)
1601+
application.save()
1602+
1603+
# TODO same as above, is this necessary?
1604+
# request = HttpRequest()
1605+
# self.client.login(request=request, username='anna', password='123456')
1606+
1607+
# TODO move to constant?
1608+
code_challenge = 'sZrievZsrYqxdnu2NVD603EiYBM18CuzZpwB-pOSZjo'
1609+
1610+
# TODO pytest.mark.parameterize
1611+
for method in ['get', 'post']:
1612+
for version in Versions.supported_versions():
1613+
print(method, version)
1614+
payload = {
1615+
'client_id': application.client_id,
1616+
'response_type': 'code',
1617+
'redirect_uri': redirect_uri,
1618+
'code_challenge': code_challenge,
1619+
'code_challenge_method': CODE_CHALLENGE_METHOD_S256,
1620+
}
1621+
# TODO same as above, first request might not be necessary
1622+
response = (getattr(self.client, method))(f'/v{version}/o/authorize', data=payload)
1623+
payload = {
1624+
'client_id': application.client_id,
1625+
'response_type': 'code',
1626+
'redirect_uri': redirect_uri,
1627+
'scope': ['capability-a'],
1628+
'expires_in': 86400,
1629+
'allow': True,
1630+
'state': '0123456789abcdef',
1631+
'code_challenge': code_challenge,
1632+
'code_challenge_method': CODE_CHALLENGE_METHOD_S256,
1633+
}
1634+
response = (getattr(self.client, method))(response['Location'], data=payload)
1635+
1636+
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
1637+
self.assertJSONEqual(
1638+
response.content,
1639+
{
1640+
'status_code': 403,
1641+
'message': APPLICATION_HAS_CLIENT_CREDENTIALS_ENABLED_NON_CLIENT_CREDENTIALS_AUTH_CALL_MADE.format(
1642+
application.name
1643+
),
1644+
},
1645+
)

apps/dot_ext/views/authorization.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
CC_SYSTEM_SOCIAL_SECURITY_NUMBER,
7777
CLIENT_ASSERTION_TYPE_VALUE,
7878
CLIENT_CREDENTIALS_SUPPORTED_TYPES,
79+
CLIENT_CREDENTIALS_TYPE,
7980
CSP_IAL_ACCEPTED_JWT_ALGORITHMS,
8081
ID_ME_URL_CONTAINS,
8182
IDME_HIGHER_ISS,
@@ -259,6 +260,15 @@ def dispatch(self, request, *args, **kwargs):
259260
status=HTTPStatus.FORBIDDEN,
260261
)
261262

263+
if self.application.allowed_auth_type == CLIENT_CREDENTIALS_TYPE:
264+
error_message = APPLICATION_HAS_CLIENT_CREDENTIALS_ENABLED_NON_CLIENT_CREDENTIALS_AUTH_CALL_MADE.format(
265+
self.application.name
266+
)
267+
return JsonResponse(
268+
{'status_code': HTTPStatus.FORBIDDEN, 'message': error_message},
269+
status=HTTPStatus.FORBIDDEN,
270+
)
271+
262272
sensitive_info_detected = self.sensitive_info_check(request)
263273

264274
# Return early 4xx HttpResponseBadRequest if illegal query parameters detected

0 commit comments

Comments
 (0)