Skip to content

Commit d575789

Browse files
authored
adding smart on fhir configuration endpoint (#1270)
* adding smart on fhir configuration endpoint * fixing pylint errors * more fixing of pylint errors * removing unnecessary fields for smart configuration response * fixing url path for smart config v2 * adding authorize-post to capabilities * removing v1 response and changing all OIDC config responses to v2 * fixing smart config url pattern * fixing linter errors * updating revocation endpoint to v2 and updating swagger openid config to match between versions * removing deprecated v2 param * updating tests to handle v2 cases * removing deprecated v2 param
1 parent 2333f50 commit d575789

File tree

8 files changed

+64
-16
lines changed

8 files changed

+64
-16
lines changed

apps/fhir/bluebutton/tests/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def test_oauth_resource_xml(self):
309309
"""
310310
request = self.factory.get('/cmsblue/fhir/v1/metadata')
311311

312-
result = build_oauth_resource(request, False, "xml")
312+
result = build_oauth_resource(request, "xml")
313313

314314
expected = "<cors>true</cors>"
315315

apps/fhir/bluebutton/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -599,14 +599,14 @@ def get_response_text(fhir_response=None):
599599
return text_in
600600

601601

602-
def build_oauth_resource(request, v2=False, format_type="json"):
602+
def build_oauth_resource(request, format_type="json"):
603603
"""
604604
Create a resource entry for oauth endpoint(s) for insertion
605605
into the conformance/capabilityStatement
606606
607607
:return: security
608608
"""
609-
endpoints = build_endpoint_info(OrderedDict(), v2, issuer=base_issuer(request))
609+
endpoints = build_endpoint_info(OrderedDict(), issuer=base_issuer(request))
610610

611611
if format_type.lower() == "xml":
612612

apps/fhir/bluebutton/views/home.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def fhir_conformance(request, via_oauth=False, v2=False, *args):
6666
od = conformance_filter(text_out)
6767

6868
# Append Security to ConformanceStatement
69-
security_endpoint = build_oauth_resource(request, v2, format_type="json")
69+
security_endpoint = build_oauth_resource(request, format_type="json")
7070
od['rest'][0]['security'] = security_endpoint
7171
# Fix format values
7272
od['format'] = ['application/json', 'application/fhir+json']

apps/wellknown/tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def test_valid_response(self):
2121
response = self.client.get(self.url)
2222
self.assertEqual(response.status_code, 200)
2323
self.assertContains(
24-
response, reverse('oauth2_provider:token'))
25-
self.assertContains(response, reverse('openid_connect_userinfo'))
24+
response, reverse('oauth2_provider_v2:token-v2'))
25+
self.assertContains(response, reverse('openid_connect_userinfo_v2'))
2626
self.assertContains(response, "response_types_supported")
2727
self.assertContains(response, getattr(settings, 'HOSTNAME_URL'))
2828
response_content = response.content

apps/wellknown/views/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .openid import openid_configuration, base_issuer, build_endpoint_info # NOQA
1+
from .openid import openid_configuration, smart_configuration, base_issuer, build_endpoint_info # NOQA
22
from .application import ApplicationListView, ApplicationLabelView # NOQA
33
from .public_applications import ApplicationListView as PublicApplicationListView # NOQA

apps/wellknown/views/openid.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
import apps.logging.request_logger as bb2logging
1010

1111
logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
12+
SCOPES_SUPPORTED = ["profile", "patient/Patient.read", "patient/ExplanationOfBenefit.read", "patient/Coverage.read"]
13+
CODE_CHALLENGE_METHODS_SUPPORTED = ["S256"]
14+
CAPABILITIES = [
15+
"client-confidential-symmetric",
16+
"sso-openid-connect",
17+
"launch-standalone",
18+
"permission-offline",
19+
"permission-patient",
20+
"permission-v1",
21+
"authorize-post"
22+
]
1223

1324

1425
@require_GET
@@ -18,8 +29,18 @@ def openid_configuration(request):
1829
"""
1930
data = OrderedDict()
2031
issuer = base_issuer(request)
21-
v2 = request.path.endswith('openid-configuration-v2') or request.path.endswith('openidConfigV2')
22-
data = build_endpoint_info(data, issuer=issuer, v2=v2)
32+
data = build_endpoint_info(data, issuer=issuer)
33+
return JsonResponse(data)
34+
35+
36+
@require_GET
37+
def smart_configuration(request):
38+
"""
39+
Views that returns smart_configuration.
40+
"""
41+
data = OrderedDict()
42+
issuer = base_issuer(request)
43+
data = build_smart_config_endpoint(data, issuer=issuer)
2344
return JsonResponse(data)
2445

2546

@@ -50,7 +71,7 @@ def base_issuer(request):
5071
return issuer
5172

5273

53-
def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):
74+
def build_endpoint_info(data=OrderedDict(), issuer=""):
5475
"""
5576
construct the data package
5677
issuer should be http: or https:// prefixed url.
@@ -60,12 +81,12 @@ def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):
6081
"""
6182
data["issuer"] = issuer
6283
data["authorization_endpoint"] = issuer + \
63-
reverse('oauth2_provider:authorize' if not v2 else 'oauth2_provider_v2:authorize-v2')
64-
data["revocation_endpoint"] = issuer + reverse('oauth2_provider:revoke')
84+
reverse('oauth2_provider_v2:authorize-v2')
85+
data["revocation_endpoint"] = issuer + reverse('oauth2_provider_v2:revoke-token-v2')
6586
data["token_endpoint"] = issuer + \
66-
reverse('oauth2_provider:token' if not v2 else 'oauth2_provider_v2:token-v2')
87+
reverse('oauth2_provider_v2:token-v2')
6788
data["userinfo_endpoint"] = issuer + \
68-
reverse('openid_connect_userinfo' if not v2 else 'openid_connect_userinfo_v2')
89+
reverse('openid_connect_userinfo_v2')
6990
data["ui_locales_supported"] = ["en-US", ]
7091
data["service_documentation"] = getattr(settings,
7192
'DEVELOPER_DOCS_URI',
@@ -82,5 +103,29 @@ def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):
82103

83104
data["response_types_supported"] = ["code", "token"]
84105
data["fhir_metadata_uri"] = issuer + \
85-
reverse('fhir_conformance_metadata' if not v2 else 'fhir_conformance_metadata_v2')
106+
reverse('fhir_conformance_metadata_v2')
107+
return data
108+
109+
110+
def build_smart_config_endpoint(data=OrderedDict(), issuer=""):
111+
"""
112+
construct the smart config endpoint response. Takes in output of build_endpoint_info since they share many fields
113+
issuer should be http: or https:// prefixed url.
114+
115+
:param data:
116+
:return:
117+
"""
118+
119+
data = build_endpoint_info(data, issuer=issuer)
120+
del (data["userinfo_endpoint"])
121+
del (data["ui_locales_supported"])
122+
del (data["service_documentation"])
123+
del (data["op_tos_uri"])
124+
del (data["fhir_metadata_uri"])
125+
data["grant_types_supported"].remove("refresh_token")
126+
127+
data["scopes_supported"] = SCOPES_SUPPORTED
128+
data["code_challenge_methods_supported"] = CODE_CHALLENGE_METHODS_SUPPORTED
129+
data["capabilities"] = CAPABILITIES
130+
86131
return data

hhs_oauth_server/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.contrib import admin
77
from apps.accounts.views.oauth2_profile import openidconnect_userinfo
88
from apps.fhir.bluebutton.views.home import fhir_conformance, fhir_conformance_v2
9+
from apps.wellknown.views.openid import smart_configuration
910
from hhs_oauth_server.hhs_oauth_server_context import IsAppInstalled
1011

1112
admin.autodiscover()
@@ -17,6 +18,7 @@
1718
urlpatterns = [
1819
path("health", include("apps.health.urls")),
1920
re_path(r"^.well-known/", include("apps.wellknown.urls")),
21+
path("v1/fhir/.wellknown/smart-configuration", smart_configuration, name="smart_configuration"),
2022
path("forms/", include("apps.forms.urls")),
2123
path("v1/accounts/", include("apps.accounts.urls")),
2224
re_path(
@@ -32,6 +34,7 @@
3234
openidconnect_userinfo,
3335
name="openid_connect_userinfo_v2",
3436
),
37+
path("v2/fhir/.wellknown/smart-configuration", smart_configuration, name="smart_configuration"),
3538
path("v2/fhir/metadata", fhir_conformance_v2, name="fhir_conformance_metadata_v2"),
3639
path("v2/fhir/", include("apps.fhir.bluebutton.v2.urls")),
3740
path("v2/o/", include("apps.dot_ext.v2.urls")),

static/openapi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ paths:
365365
description: "Error: Bad Gateway, e.g. An error occurred contacting the FHIR server."
366366
tags:
367367
- v1
368-
/.well-known/openid-configuration-v2:
368+
/.well-known/openid-configuration:
369369
get:
370370
operationId: openIdConfig_v2
371371
description: "Returns OIDC (OpenID Connect protocol) Discovery: listing of the OpenID/OAuth endpoints, supported scopes and claims (public access, no token needed)"

0 commit comments

Comments
 (0)