Skip to content

Commit

Permalink
Openemr inferno fixes for #5827 #5828 #5829 #5830 #5831 #5833 #5834 (#…
Browse files Browse the repository at this point in the history
…5836)

* OpenEMR FHIR Api bug/feature fixes

Fixes #5831 - capabilities statement add passthrough statement
Fixes #5830 - add document title to DocumentReference endpoint
Fixes #5829 - enable patients to download their own documents.
Fixes #5828 - OperationDefinition endpoint, $bulkdata-status updates
Fixes #5827 - Migrate FHIR doc binary download to root Binary endpoint
Fixes #5833 - Patient portal scopes independent of fhir scopes

* Fix scope permission on insert/create/delete

Fixes #5834 - Add delete interaction in capability statement to handle
write scopes correctly.

We were using the wrong interaction name for the capability statement
and I renamed it in the prior commit.  However, I wasn't handling it in
our ScopeRepository correctly.  Changed the interaction reference to be
'create' instead of 'insert' and put in a documentation url of where to
go to get the reference.

* Fix styles and unit tests

* Fix missing definition URL

Found the definition url was missing for the operation.  Not sure how I
ended up removing it.
  • Loading branch information
adunsulag authored Oct 18, 2022
1 parent 7eda47d commit 42651b9
Show file tree
Hide file tree
Showing 20 changed files with 419 additions and 71 deletions.
6 changes: 3 additions & 3 deletions API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ This is a listing of scopes:
- `api:fhir` (fhir which are the /fhir/ endpoints)
- `patient/AllergyIntolerance.read`
- `patient/Appointment.read`
- `patient/Binary.read`
- `patient/CarePlan.read`
- `patient/CareTeam.read`
- `patient/Condition.read`
- `patient/Coverage.read`
- `patient/Device.read`
- `patient/DiagnosticReport.read`
- `patient/Document.read`
- `patient/DocumentReference.read`
- `patient/DocumentReference.$docref`
- `patient/Encounter.read`
Expand All @@ -90,13 +90,13 @@ This is a listing of scopes:
- `patient/Procedure.read`
- `patient/Provenance.read`
- `system/AllergyIntolerance.read`
- `system/Binary.read`
- `system/CarePlan.read`
- `system/CareTeam.read`
- `system/Condition.read`
- `system/Coverage.read`
- `system/Device.read`
- `system/DiagnosticReport.read`
- `system/Document.read`
- `system/DocumentReference.read`
- `system/DocumentReference.$docref`
- `system/Encounter.read`
Expand All @@ -119,13 +119,13 @@ This is a listing of scopes:
- `system/*.$bulkdata-status`
- `system/*.$export`
- `user/AllergyIntolerance.read`
- `user/Binary.read`
- `user/CarePlan.read`
- `user/CareTeam.read`
- `user/Condition.read`
- `user/Coverage.read`
- `user/Device.read`
- `user/DiagnosticReport.read`
- `user/Document.read`
- `user/DocumentReference.read`
- `user/DocumentReference.$docref`
- `user/Encounter.read`
Expand Down
16 changes: 8 additions & 8 deletions FHIR_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ A status Query will return a result like the following:
"requiresAccessToken": true,
"output": [
{
"url": "https:\/\/localhost:9300\/apis\/default\/fhir\/Document\/97552\/Binary",
"url": "https:\/\/localhost:9300\/apis\/default\/fhir\/Binary\/97552",
"type": "Patient"
},
{
"url": "https:\/\/localhost:9300\/apis\/default\/fhir\/Document\/105232\/Binary",
"url": "https:\/\/localhost:9300\/apis\/default\/fhir\/Binary\/105232",
"type": "Encounter"
}
],
Expand All @@ -145,15 +145,15 @@ A status Query will return a result like the following:

You can download the exported documents which are formatted in Newline Delimited JSON (NDJSON) by making a call to:
```sh
curl -X GET 'https://localhost:9300/apis/default/fhir/Document/105232/Binary'
curl -X GET 'https://localhost:9300/apis/default/fhir/Binary/105232'
```

In order to download the documents you will need the **system/Document.read** scope.
In order to download the documents you will need the **system/Binary.read** scope.

#### Bulk FHIR Scope Reference
- All System export - **system/\*.$export system\*.$bulkdata-status system/Document.read**
- Group System export - **system/Group.$export system\*.$bulkdata-status system/Document.read**
- Patient System export - **system/Patient.$export system\*.$bulkdata-status system/Document.read**
- All System export - **system/\*.$export system\*.$bulkdata-status system/Binary.read**
- Group System export - **system/Group.$export system\*.$bulkdata-status system/Binary.read**
- Patient System export - **system/Patient.$export system\*.$bulkdata-status system/Binary.read**

####
## 3rd Party SMART Apps
Expand Down Expand Up @@ -205,7 +205,7 @@ It is recommended that native applications follow best practices for native clie
### Generate CCDA
- [Tutorial to Generate CCDA (with Screenshots)](https://github.com/openemr/openemr/issues/5284#issuecomment-1155678620)
### Details Docref
- Requires <context>/DocumentReference.$docref, <context>/DocumentReference.read, and <context>/Document.read scopes
- Requires <context>/DocumentReference.$docref, <context>/DocumentReference.read, and <context>/Binary.read scopes
- Start and end date filter encounter related events for the following sections:
- History of Procedures
- Relevant DX Tests / LAB Data
Expand Down
10 changes: 8 additions & 2 deletions _rest_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,15 @@ public static function skipApiAuth($resource): bool
exit();
}
// let the capability statement for FHIR or the SMART-on-FHIR through
$resource = str_replace('/' . self::$SITE, '', $resource);
if (
$resource === ("/" . self::$SITE . "/fhir/metadata") ||
$resource === ("/" . self::$SITE . "/fhir/.well-known/smart-configuration")
// TODO: @adunsulag we need to centralize our auth skipping logic... as we have this duplicated in HttpRestRouteHandler
// however, at the point of this method we don't have the resource identified and haven't gone through our parsing
// routine to handle that logic...
$resource === ("/fhir/metadata") ||
$resource === ("/fhir/.well-known/smart-configuration") ||
// skip list and single instance routes
0 === strpos("/fhir/OperationDefinition", $resource)
) {
return true;
} else {
Expand Down
125 changes: 110 additions & 15 deletions _rest_routes.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
* "api:fhir": "FHIR R4 API",
* "patient/AllergyIntolerance.read": "Read allergy intolerance resources for the current patient (api:fhir)",
* "patient/Appointment.read": "Read appointment resources for the current patient (api:fhir)",
* "patient/Binary.read": "Read binary document resources for the current patient (api:fhir)",
* "patient/CarePlan.read": "Read care plan resources for the current patient (api:fhir)",
* "patient/CareTeam.read": "Read care team resources for the current patient (api:fhir)",
* "patient/Condition.read": "Read condition resources for the current patient (api:fhir)",
* "patient/Coverage.read": "Read coverage resources for the current patient (api:fhir)",
* "patient/Device.read": "Read device resources for the current patient (api:fhir)",
* "patient/DiagnosticReport.read": "Read diagnostic report resources for the current patient (api:fhir)",
* "patient/Document.read": "Read document resources for the current patient (api:fhir)",
* "patient/DocumentReference.read": "Read document reference resources for the current patient (api:fhir)",
* "patient/DocumentReference.$docref" : "Generate a document for the current patient or returns the most current Clinical Summary of Care Document (CCD)",
* "patient/Encounter.read": "Read encounter resources for the current patient (api:fhir)",
Expand All @@ -58,13 +58,13 @@
* "patient/Procedure.read": "Read procedure resources for the current patient (api:fhir)",
* "patient/Provenance.read": "Read provenance resources for the current patient (api:fhir)",
* "system/AllergyIntolerance.read": "Read all allergy intolerance resources in the system (api:fhir)",
* "system/Binary.read": "Read all binary document resources in the system (api:fhir)",
* "system/CarePlan.read": "Read all care plan resources in the system (api:fhir)",
* "system/CareTeam.read": "Read all care team resources in the system (api:fhir)",
* "system/Condition.read": "Read all condition resources in the system (api:fhir)",
* "system/Coverage.read": "Read all coverage resources in the system (api:fhir)",
* "system/Device.read": "Read all device resources in the system (api:fhir)",
* "system/DiagnosticReport.read": "Read all diagnostic report resources in the system (api:fhir)",
* "system/Document.read": "Read all document resources in the system (api:fhir)",
* "system/DocumentReference.read": "Read all document reference resources in the system (api:fhir)",
* "system/DocumentReference.$docref" : "Generate a document for any patient in the system or returns the most current Clinical Summary of Care Document (CCD)",
* "system/Encounter.read": "Read all encounter resources in the system (api:fhir)",
Expand All @@ -83,13 +83,13 @@
* "system/Procedure.read": "Read all procedure resources in the system (api:fhir)",
* "system/Provenance.read": "Read all provenance resources in the system (api:fhir)",
* "user/AllergyIntolerance.read": "Read all allergy intolerance resources the user has access to (api:fhir)",
* "user/Binary.read" : "Read all binary documents the user has access to (api:fhir)",
* "user/CarePlan.read": "Read all care plan resources the user has access to (api:fhir)",
* "user/CareTeam.read": "Read all care team resources the user has access to (api:fhir)",
* "user/Condition.read": "Read all condition resources the user has access to (api:fhir)",
* "user/Coverage.read": "Read all coverage resources the user has access to (api:fhir)",
* "user/Device.read": "Read all device resources the user has access to (api:fhir)",
* "user/DiagnosticReport.read": "Read all diagnostic report resources the user has access to (api:fhir)",
* "user/Document.read" : "Read all documents the user has access to (api:fhir)",
* "user/DocumentReference.read": "Read all document reference resources the user has access to (api:fhir)",
* "user/DocumentReference.$docref" : "Generate a document for any patient the user has access to or returns the most current Clinical Summary of Care Document (CCD) (api:fhir)",
* "user/Encounter.read": "Read all encounter resources the user has access to (api:fhir)",
Expand Down Expand Up @@ -7094,6 +7094,7 @@
use OpenEMR\RestControllers\FHIR\FhirMetaDataRestController;
use OpenEMR\RestControllers\FHIR\Operations\FhirOperationExportRestController;
use OpenEMR\RestControllers\FHIR\Operations\FhirOperationDocRefRestController;
use OpenEMR\RestControllers\FHIR\Operations\FhirOperationDefinitionRestController;

// Note that the fhir route includes both user role and patient role
// (there is a mechanism in place to ensure patient role is binded
Expand Down Expand Up @@ -8732,7 +8733,7 @@
* {
* "attachment": {
* "contentType": "image/gif",
* "url": "https://localhost:9300/apis/default/fhir/Document/7/Binary"
* "url": "https://localhost:9300/apis/default/fhir/Binary/7"
* },
* "format": {
* "system": "http://ihe.net/fhir/ValueSet/IHE.FormatCode.codesystem",
Expand Down Expand Up @@ -8775,7 +8776,7 @@

/**
* @OA\Get(
* path="/fhir/Document/{id}/Binary",
* path="/fhir/Binary/{id}",
* description="Used for downloading binary documents generated either with BULK FHIR Export or with the $docref CCD export operation. Documentation can be found at <a href='https://www.open-emr.org/wiki/index.php/OpenEMR_Wiki_Home_Page#API' target='_blank' rel='noopener'>https://www.open-emr.org/wiki/index.php/OpenEMR_Wiki_Home_Page#API</a>",
* tags={"fhir"},
* @OA\Parameter(
Expand All @@ -8802,15 +8803,16 @@
* security={{"openemr_auth":{}}}
* )
*/
'GET /fhir/Document/:id/Binary' => function ($documentId, HttpRestRequest $request) {
// TODO: @adunsulag we need to be able to retrieve our CCDA documents this way...
// currently only allow users with the same permissions as export to take a file out
// this could be relaxed to allow other types of files ie such as patient access etc.
RestConfig::authorization_check("admin", "users");

// Grab the document id
'GET /fhir/Binary/:id' => function ($documentId, HttpRestRequest $request) {
$docController = new \OpenEMR\RestControllers\FHIR\FhirDocumentRestController($request);
$response = $docController->downloadDocument($documentId);

if ($request->isPatientRequest()) {
$response = $docController->downloadDocument($documentId, $request->getPatientUUIDString());
} else {
RestConfig::authorization_check("admin", "users");
$response = $docController->downloadDocument($documentId);
}

return $response;
},

Expand Down Expand Up @@ -11584,8 +11586,20 @@
* )
*/
"GET /fhir/Person/:uuid" => function ($uuid, HttpRestRequest $request) {
RestConfig::authorization_check("admin", "users");
$return = (new FhirPersonRestController())->getOne($uuid);
// if the api user is requesting their own user we need to let it through
// this is because the /Person endpoint needs to be responsive to the fhirUser return value
// for the currently logged in user
if ($request->getRequestUserUUIDString() == $uuid) {
$return = (new FhirPersonRestController())->getOne($uuid);
} else if (!$request->isPatientRequest()) {
// not a patient ,make sure we have access to the users ACL
RestConfig::authorization_check("admin", "users");
$return = (new FhirPersonRestController())->getOne($uuid);
} else {
// if we are a patient bound request we need to make sure we are only bound to the patient
$return = (new FhirPersonRestController())->getOne($uuid, $request->getPatientUUIDString());
}

RestConfig::apiLog($return);
return $return;
},
Expand Down Expand Up @@ -12541,6 +12555,87 @@
return $return;
},

/**
* @OA\Get(
* path="/fhir/OperationDefinition",
* description="Returns a list of the OperationDefinition resources that are specific to this OpenEMR installation",
* tags={"fhir"},
* @OA\Response(
* response="200",
* description="Return list of OperationDefinition resources"
* )
* )
*/
"GET /fhir/OperationDefinition" => function (HttpRestRequest $request) {
// for now we will just hard code the custom resources
$operationDefinitionController = new FhirOperationDefinitionRestController();
$return = $operationDefinitionController->getAll($request->getQueryParams());
RestConfig::apiLog($return);
return $return;
},

/**
* @OA\Get(
* path="/fhir/OperationDefinition/{operation}",
* description="Returns a single OperationDefinition resource that is specific to this OpenEMR installation",
* tags={"fhir"},
* @OA\Parameter(
* name="operation",
* in="path",
* description="The name of the operation to query. For example $bulkdata-status",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(
* response="200",
* description="Standard Response",
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* @OA\Property(
* property="json object",
* description="FHIR Json object.",
* type="object"
* ),
* example={
* "resourceType": "OperationDefinition",
* "name": "$bulkdata-status",
* "status": "active",
* "kind": "operation",
* "parameter": {
* {
* "name": "job",
* "use": "in",
* "min": 1,
* "max": 1,
* "type": {
* "system": "http://hl7.org/fhir/data-types",
* "code": "string",
* "display": "string"
* },
* "searchType": {
* "system": "http://hl7.org/fhir/ValueSet/search-param-type",
* "code": "string",
* "display": "string"
* }
* }
* }
* }
* )
* )
* ),
* )
*/
"GET /fhir/OperationDefinition/:operation" => function ($operation, HttpRestRequest $request) {
// for now we will just hard code the custom resources
$operationDefinitionController = new FhirOperationDefinitionRestController();
$return = $operationDefinitionController->getOne($operation);
RestConfig::apiLog($return);
return $return;
},

// FHIR root level operations

/**
Expand Down
Loading

0 comments on commit 42651b9

Please sign in to comment.