Skip to content

Commit

Permalink
Merge pull request #13 from miracum/add-url-passthrough
Browse files Browse the repository at this point in the history
feat: enable passthrough urls and resources
  • Loading branch information
BoehmDo authored Oct 24, 2024
2 parents 755adce + ae0d617 commit 806efbd
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 3 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
| BA_USER_NAME | | BasicAuth username if required for the connection to the fhir server | |
| BA_PASSWORD | | BasicAuth password if required for the connection to the fhir server | |
| RESOURCE_CONFIG | | Multiline yaml, analogue to the resource config file | |
| PASSTHROUGH_CONFIG | | Multiline yaml, analogue to the passthrough config file | |
| PROVISION_CONFIG | | Multiline json, analogue to the provison config file | |
| SSL_CERT | | Certificate for https | |
| SSL_KEY | | Key for https | |
Expand Down Expand Up @@ -77,7 +78,7 @@ Resources:
### Provision Configuration
Provisions can be configured either before container startup in the config/general_provison_config.json or during runtime:
Pass a List of provision codes in a json format based on the [MII Kerndatensatz](https://simplifier.net/packages/de.medizininformatikinitiative.kerndatensatz.consent/1.0.0-ballot1).
Every Consent is required to have ALL provided provisions as a subset of its provisions. This structure is required for the preconfiguration as well as the parameter version.
Every Patient is required to have ALL provided provisions as a subset of its provisions. This structure is required for the preconfiguration as well as the parameter version.

#### Example:
```
Expand All @@ -94,3 +95,15 @@ Every Consent is required to have ALL provided provisions as a subset of its pro
]
}
```

### Passthrough Configuration:
Some Applications might require you to additionally access proprietary endpoints in the underlying FHIR-Server. Similar to the previous configuration this can be achieved through a yaml configuration, that can be provided via environment variable, as well as the passthrough_config.yml file. You only need to provide the URL in a example route "fhir.server.url:portURL". If the url contains the usual /fhir/ base it has to be supplied under Resources, where "fhir.server.url:port/fhir/Resource".

#### Example:
```
URLs:
- "/metadata"
- "/test/echo"
Resources:
- "metadata"
```
18 changes: 16 additions & 2 deletions facade_app/api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import os, yaml
from flask import Flask
from flask_restful import Api
from resources.fhir_facade_passthrough import FHIR_Facade_Passthrough
from resources.fhir_facade_server import FHIR_Facade_Server

temp = os.getenv("PASSTHROUGH_CONFIG", "")
if temp != "":
pass_conf = yaml.safe_load(temp)
else:
with open("../config/passthrough_config.yml") as cfgfile:
pass_conf = yaml.safe_load(cfgfile.read())

app = Flask(__name__)
api = Api(app)

# set up web server at FACADE_URL+FACADE_PORT
api.add_resource(FHIR_Facade_Server, '/fhir/<string:resource>', '/fhir/<string:resource>/<string:search>')

api.add_resource(
FHIR_Facade_Server,
"/fhir/<string:resource>",
"/fhir/<string:resource>/<string:search>",
)
if len(pass_conf["URLs"]) > 0:
api.add_resource(FHIR_Facade_Passthrough, *pass_conf["URLs"])
8 changes: 8 additions & 0 deletions facade_app/config/passthrough_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Add all endpoints and resources that are supposed to be passed through without consent checks
# Endpoints that use the same /fhir/ base url as resources have to be added in the resources section
# E.g. /fhir/metadata
URLs:
- "/metadata"
- "/test/echo"
Resources:
- "metadata"
23 changes: 23 additions & 0 deletions facade_app/resources/fhir_facade_passthrough.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from resources.util.util_functions import get_passthrough_result
from flask import request, Response
from flask_restful import Resource


def passthrough_handle_request(self, is_post):
params = request.args.copy()
headers = dict(request.headers)
data = bytes(request.data)

if is_post:
return get_passthrough_result(request.full_path, params, headers, data, True)
else:
return get_passthrough_result(request.full_path, params, headers, data, False)


class FHIR_Facade_Passthrough(Resource):

def get(self):
return passthrough_handle_request(self, False)

def post(self):
return passthrough_handle_request(self, True)
12 changes: 12 additions & 0 deletions facade_app/resources/fhir_facade_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import yaml, json, requests, os, math, time, multiprocessing, logging
from requests.auth import HTTPBasicAuth
from functools import partial
from resources.fhir_facade_passthrough import passthrough_handle_request
import uuid, shortuuid
import flask
from flask import request, Response
Expand All @@ -23,6 +24,14 @@
with open("../config/resource_config.yml") as cfgfile:
resource_config = yaml.safe_load(cfgfile)

temp = os.getenv("PASSTHROUGH_CONFIG", "")
if temp != "":
pass_config = yaml.safe_load(temp)
else:
with open("../config/passthrough_config.yml") as cfgfile:
pass_config = yaml.safe_load(cfgfile)

PASS_RESOURCES = pass_config["Resources"]
RESOURCE_PATHS = resource_config["Resources"]
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")

Expand All @@ -44,6 +53,9 @@ def handleRequest(self, resource, search=""):
if resource == "Page" and "__page-id" in params:
return getPage(params["__page-id"])

if resource in PASS_RESOURCES:
return passthrough_handle_request(self, False)

# Return error code if request is invalid
if search != "_search":
return "Syntax error. Use the appropriate fhir-post-search syntax: .../fhir/type/_search"
Expand Down
45 changes: 45 additions & 0 deletions facade_app/resources/util/util_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os, requests, json
from requests.auth import HTTPBasicAuth


def get_passthrough_result(url, params, headers, data, is_post):
SERVER_URL = os.getenv("FHIR_SERVER_URL", "")

s = requests.sessions.Session()
auth = HTTPBasicAuth(os.getenv("BA_USER_NAME", ""), os.getenv("BA_PASSWORD", ""))

# Merge params and jsondata
params["_format"] = "application/fhir+json"
try:
data = json.loads(data)
data.update(params)
except:
data = {}
data.update(params)

headers.update(
{
"Accept": "application/fhir+json",
"Content-Type": "application/x-www-form-urlencoded",
}
)

if is_post:
response = s.post(
SERVER_URL.replace("/fhir/", "") + url,
auth=auth,
headers=headers,
params=data,
verify=False,
).json()

else:
response = s.get(
SERVER_URL.replace("/fhir/", "") + url,
auth=auth,
headers=headers,
params=data,
verify=False,
).json()

return response

0 comments on commit 806efbd

Please sign in to comment.