Skip to content

Commit

Permalink
adding smart on fhir configuration endpoint (#1270)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bwang-icf authored Dec 3, 2024
1 parent 2333f50 commit d575789
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 16 deletions.
2 changes: 1 addition & 1 deletion apps/fhir/bluebutton/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def test_oauth_resource_xml(self):
"""
request = self.factory.get('/cmsblue/fhir/v1/metadata')

result = build_oauth_resource(request, False, "xml")
result = build_oauth_resource(request, "xml")

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

Expand Down
4 changes: 2 additions & 2 deletions apps/fhir/bluebutton/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,14 +599,14 @@ def get_response_text(fhir_response=None):
return text_in


def build_oauth_resource(request, v2=False, format_type="json"):
def build_oauth_resource(request, format_type="json"):
"""
Create a resource entry for oauth endpoint(s) for insertion
into the conformance/capabilityStatement
:return: security
"""
endpoints = build_endpoint_info(OrderedDict(), v2, issuer=base_issuer(request))
endpoints = build_endpoint_info(OrderedDict(), issuer=base_issuer(request))

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

Expand Down
2 changes: 1 addition & 1 deletion apps/fhir/bluebutton/views/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def fhir_conformance(request, via_oauth=False, v2=False, *args):
od = conformance_filter(text_out)

# Append Security to ConformanceStatement
security_endpoint = build_oauth_resource(request, v2, format_type="json")
security_endpoint = build_oauth_resource(request, format_type="json")
od['rest'][0]['security'] = security_endpoint
# Fix format values
od['format'] = ['application/json', 'application/fhir+json']
Expand Down
4 changes: 2 additions & 2 deletions apps/wellknown/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def test_valid_response(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertContains(
response, reverse('oauth2_provider:token'))
self.assertContains(response, reverse('openid_connect_userinfo'))
response, reverse('oauth2_provider_v2:token-v2'))
self.assertContains(response, reverse('openid_connect_userinfo_v2'))
self.assertContains(response, "response_types_supported")
self.assertContains(response, getattr(settings, 'HOSTNAME_URL'))
response_content = response.content
Expand Down
2 changes: 1 addition & 1 deletion apps/wellknown/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .openid import openid_configuration, base_issuer, build_endpoint_info # NOQA
from .openid import openid_configuration, smart_configuration, base_issuer, build_endpoint_info # NOQA
from .application import ApplicationListView, ApplicationLabelView # NOQA
from .public_applications import ApplicationListView as PublicApplicationListView # NOQA
61 changes: 53 additions & 8 deletions apps/wellknown/views/openid.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
import apps.logging.request_logger as bb2logging

logger = logging.getLogger(bb2logging.HHS_SERVER_LOGNAME_FMT.format(__name__))
SCOPES_SUPPORTED = ["profile", "patient/Patient.read", "patient/ExplanationOfBenefit.read", "patient/Coverage.read"]
CODE_CHALLENGE_METHODS_SUPPORTED = ["S256"]
CAPABILITIES = [
"client-confidential-symmetric",
"sso-openid-connect",
"launch-standalone",
"permission-offline",
"permission-patient",
"permission-v1",
"authorize-post"
]


@require_GET
Expand All @@ -18,8 +29,18 @@ def openid_configuration(request):
"""
data = OrderedDict()
issuer = base_issuer(request)
v2 = request.path.endswith('openid-configuration-v2') or request.path.endswith('openidConfigV2')
data = build_endpoint_info(data, issuer=issuer, v2=v2)
data = build_endpoint_info(data, issuer=issuer)
return JsonResponse(data)


@require_GET
def smart_configuration(request):
"""
Views that returns smart_configuration.
"""
data = OrderedDict()
issuer = base_issuer(request)
data = build_smart_config_endpoint(data, issuer=issuer)
return JsonResponse(data)


Expand Down Expand Up @@ -50,7 +71,7 @@ def base_issuer(request):
return issuer


def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):
def build_endpoint_info(data=OrderedDict(), issuer=""):
"""
construct the data package
issuer should be http: or https:// prefixed url.
Expand All @@ -60,12 +81,12 @@ def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):
"""
data["issuer"] = issuer
data["authorization_endpoint"] = issuer + \
reverse('oauth2_provider:authorize' if not v2 else 'oauth2_provider_v2:authorize-v2')
data["revocation_endpoint"] = issuer + reverse('oauth2_provider:revoke')
reverse('oauth2_provider_v2:authorize-v2')
data["revocation_endpoint"] = issuer + reverse('oauth2_provider_v2:revoke-token-v2')
data["token_endpoint"] = issuer + \
reverse('oauth2_provider:token' if not v2 else 'oauth2_provider_v2:token-v2')
reverse('oauth2_provider_v2:token-v2')
data["userinfo_endpoint"] = issuer + \
reverse('openid_connect_userinfo' if not v2 else 'openid_connect_userinfo_v2')
reverse('openid_connect_userinfo_v2')
data["ui_locales_supported"] = ["en-US", ]
data["service_documentation"] = getattr(settings,
'DEVELOPER_DOCS_URI',
Expand All @@ -82,5 +103,29 @@ def build_endpoint_info(data=OrderedDict(), v2=False, issuer=""):

data["response_types_supported"] = ["code", "token"]
data["fhir_metadata_uri"] = issuer + \
reverse('fhir_conformance_metadata' if not v2 else 'fhir_conformance_metadata_v2')
reverse('fhir_conformance_metadata_v2')
return data


def build_smart_config_endpoint(data=OrderedDict(), issuer=""):
"""
construct the smart config endpoint response. Takes in output of build_endpoint_info since they share many fields
issuer should be http: or https:// prefixed url.
:param data:
:return:
"""

data = build_endpoint_info(data, issuer=issuer)
del (data["userinfo_endpoint"])
del (data["ui_locales_supported"])
del (data["service_documentation"])
del (data["op_tos_uri"])
del (data["fhir_metadata_uri"])
data["grant_types_supported"].remove("refresh_token")

data["scopes_supported"] = SCOPES_SUPPORTED
data["code_challenge_methods_supported"] = CODE_CHALLENGE_METHODS_SUPPORTED
data["capabilities"] = CAPABILITIES

return data
3 changes: 3 additions & 0 deletions hhs_oauth_server/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.contrib import admin
from apps.accounts.views.oauth2_profile import openidconnect_userinfo
from apps.fhir.bluebutton.views.home import fhir_conformance, fhir_conformance_v2
from apps.wellknown.views.openid import smart_configuration
from hhs_oauth_server.hhs_oauth_server_context import IsAppInstalled

admin.autodiscover()
Expand All @@ -17,6 +18,7 @@
urlpatterns = [
path("health", include("apps.health.urls")),
re_path(r"^.well-known/", include("apps.wellknown.urls")),
path("v1/fhir/.wellknown/smart-configuration", smart_configuration, name="smart_configuration"),
path("forms/", include("apps.forms.urls")),
path("v1/accounts/", include("apps.accounts.urls")),
re_path(
Expand All @@ -32,6 +34,7 @@
openidconnect_userinfo,
name="openid_connect_userinfo_v2",
),
path("v2/fhir/.wellknown/smart-configuration", smart_configuration, name="smart_configuration"),
path("v2/fhir/metadata", fhir_conformance_v2, name="fhir_conformance_metadata_v2"),
path("v2/fhir/", include("apps.fhir.bluebutton.v2.urls")),
path("v2/o/", include("apps.dot_ext.v2.urls")),
Expand Down
2 changes: 1 addition & 1 deletion static/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ paths:
description: "Error: Bad Gateway, e.g. An error occurred contacting the FHIR server."
tags:
- v1
/.well-known/openid-configuration-v2:
/.well-known/openid-configuration:
get:
operationId: openIdConfig_v2
description: "Returns OIDC (OpenID Connect protocol) Discovery: listing of the OpenID/OAuth endpoints, supported scopes and claims (public access, no token needed)"
Expand Down

0 comments on commit d575789

Please sign in to comment.