Closed mattStorer closed 1 month ago
This is thrown from code in edu.ohsu.cmp.ecp.sds.UserIdentity
:
private void requireMatchingIdType( IIdType id ) {
if ( !this.userResourceType.equalsIgnoreCase( id.getResourceType() ) )
...
}
This means that the authorized patient id, which is determined from the introspect response, is lacking a resource type.
This would happen if using the introspect endpoint is returning "sub": "abcd-1234"
instead of "sub": "Patient/abcd-1234"
or "sub": "Practitioner/efgh-5678"
@timcoffman yes, that tracks. This is an example of the introspect response for an eCarePlanner-generated access token:
{
"active": true,
"scope": "user/CarePlan.read user/CareTeam.read user/Condition.read user/DiagnosticReport.read user/Encounter.read user/Flag.read user/Goal.read user/Immunization.read user/Location.read user/Medication.read user/MedicationRequest.read user/Observation.read user/Organization.read user/Patient.read user/Practitioner.read user/PractitionerRole.read user/Procedure.read user/Provenance.read user/Questionnaire.read user/QuestionnaireResponse.Read user/RelatedPerson.read user/ServiceRequest.read user/Task.read launch/patient",
"client_id": "7666b9ec-8caa-43bd-8231-2eea4f5d281d",
"username": "STORER",
"exp": 1727108173,
"sub": "https://epicmobile.ohsu.edu/FHIRDEV/api/FHIR/R4/Practitioner/eUNWe-TcUNZJgyQxTiyPyWA3",
"iss": "https://epicmobile.ohsu.edu/FHIRDEV/oauth2",
"jti": "D876CC94DDAF4601953E6A1789523056",
"epic_user_type": "EMP",
"patient": "edBu5co.Z6y7bjOHAfSfm8hXaEzYfFgw4WKpc.A6WwP83",
"fhirUser": "https://epicmobile.ohsu.edu/FHIRDEV/api/FHIR/R4/Practitioner/eUNWe-TcUNZJgyQxTiyPyWA3"
}
Note the patient element, which is just the ID part, and contains no resource type.
I think in this case, because "patient" is specified in the element name, that in this case it would be safe to prepend the ID with "Patient/" for use in downstream operations.
Is that doable?
OK, that explains it. The same code for identifying linked compartments is used for this purpose, so what you just identified is triggering it. My tests all included a Patient/
prefix.
I've crafted an update, will push it momentarily.
nice! I've pulled your update and am building it now
just tested, still throwing a 500 error, but the error changed.
new Linkage query response:
{
"resourceType": "OperationOutcome",
"issue": [ {
"severity": "error",
"code": "processing",
"diagnostics": "cannot build UserIdentity when basisUserId is missing the resource type (\"eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03\")"
} ]
}
Stack trace:
2024-09-24 13:54:19.890 [http-nio-50103-exec-7] ERROR c.u.f.r.s.i.ExceptionHandlingInterceptor [ExceptionHandlingInterceptor.java:219] Failure during REST processing: java.lang.NullPointerException: cannot build UserIdentity when basisUserId is missing the resource type ("eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03")
java.lang.NullPointerException: cannot build UserIdentity when basisUserId is missing the resource type ("eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03")
at java.base/java.util.Objects.requireNonNull(Objects.java:235)
at edu.ohsu.cmp.ecp.sds.UserIdentity.<init>(UserIdentity.java:29)
at edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.buildUserIdentity(SupplementalDataStorePermissionsInterceptor.java:95)
at edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.buildUserIdentity(SupplementalDataStorePermissionsInterceptor.java:79)
at edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.permissionsForPractitionerInPatientContext(SupplementalDataStorePermissionsInterceptor.java:111)
at edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.permissionsForPractitioner(SupplementalDataStorePermissionsInterceptor.java:100)
at edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.identifyPermissions(SupplementalDataStorePermissionsInterceptor.java:65)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at ca.uhn.fhir.interceptor.executor.BaseInterceptorService$HookInvoker.invoke(BaseInterceptorService.java:550)
at ca.uhn.fhir.interceptor.executor.BaseInterceptorService.doCallHooks(BaseInterceptorService.java:287)
at ca.uhn.fhir.interceptor.executor.BaseInterceptorService.callHooks(BaseInterceptorService.java:275)
at ca.uhn.fhir.interceptor.executor.BaseInterceptorService.callHooks(BaseInterceptorService.java:63)
at ca.uhn.fhir.rest.server.method.PageMethodBinding.callPreHandledHooks(PageMethodBinding.java:177)
at ca.uhn.fhir.rest.server.method.BaseMethodBinding.invokeServerMethod(BaseMethodBinding.java:258)
at ca.uhn.fhir.rest.server.method.SearchMethodBinding.invokeServer(SearchMethodBinding.java:311)
at ca.uhn.fhir.rest.server.method.SearchMethodBinding.invokeServer(SearchMethodBinding.java:53)
at ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding.doInvokeServer(BaseResourceReturningMethodBinding.java:146)
at ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding.invokeServer(BaseResourceReturningMethodBinding.java:275)
at ca.uhn.fhir.rest.server.RestfulServer.handleRequest(RestfulServer.java:1190)
at ca.uhn.fhir.rest.server.RestfulServer.doGet(RestfulServer.java:420)
at ca.uhn.fhir.rest.server.RestfulServer.service(RestfulServer.java:1923)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:142)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:840)
excellent! Linkage resources are now coming through correctly. however, what's happening now is that 403 Forbidden errors are getting thrown for Goals, Conditions, and QuestionnaireResponses:
Goal request:
Goal response body:
{
"resourceType": "OperationOutcome",
"issue": [ {
"severity": "error",
"code": "processing",
"diagnostics": "HAPI-0333: Access denied by rule: no access rules grant permission"
} ]
}
I confirm that local patient 40182467-621e-42f9-adcc-d4583d4a672b
is the correct one for Rose Fhir (who has OHSU POC patient ID eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03
):
This is the access token introspect response for the current session - as we can see, the patient element contains Rose Fhir's patient ID eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03
, which connects via Linkage above to local Patient resource 40182467-621e-42f9-adcc-d4583d4a672b
:
Linkage:
{
"resourceType": "Linkage",
"item": [
{
"type": "source",
"resource": {
"extension": [
{
"url": "urn:sds:partition-name",
"valueUrl": "SDS-LOCAL"
}
],
"reference": "Patient/40182467-621e-42f9-adcc-d4583d4a672b"
}
},
{
"type": "alternate",
"resource": {
"extension": [
{
"url": "urn:sds:partition-name",
"valueUrl": "https://epicmobile.ohsu.edu/FHIRDEV/api/FHIR/R4"
}
],
"reference": "Patient/eFTHaVbQzCEwOEE97maN2MC2jJi-r8nnkhRh.umMUlz03"
}
}
]
}
Thoughts on why this is throwing 403 Forbidden?
Testing recent SDS updates, it looks like Linkage queries are now no longer getting Linkage resources for other patients, which is great.
However, it does appear that each Linkage that's queried for, is the same one that comes back -
I think this may have to do with the SDS returning Linkage resources associated with the OHSU Patient ID and not the Local patient ID, so it may need to be the case that the SDS would need to query for the Local Patient ID first, and then get all Linkages associated with that.
I think that must be what's going on, which is unfortunate because it would require annoying additional custom coding to be embedded in the SDS.
Thoughts?
A thought : when processing a Practitioner-based access token as we do in eCarePlanner, maybe it would just involve an extra search for Linkage for that specified ID, grab the source patient ID from that, and then work with that for all downstream operations?
Would that work? It seems that could be a clean translation.
Thoughts?
I think I know why we're getting these 403 Forbidden errors!
My thought is that because eCarePlanner is requesting resources for the local Patient ID and getting "access denied" in response - and given what I'm seeing with Linkages above where evidently the only Linkages returned are those associated with the OHSU POC patient ID - it may be that those 403s are a result of the specified local Patient ID being different from the OHSU POC patient ID from the access token, and the SDS not compiling a set of authorized Patient IDs as a result?
How does the SDS operate when it's working with MyCarePlanner and regular Patient-based access tokens?
I'm not sure this is the actual cause, but it sure smells like these things are related.
this is fixed!
SDS is throwing 500 Internal Server Errors when eCarePlanner attempts to query Linkage resources:
Response body:
Stack trace: