From dd85cc53f0467f8c601a8c3eab1258bdb5614a8e Mon Sep 17 00:00:00 2001 From: Ming Date: Fri, 9 Feb 2024 18:40:23 +0700 Subject: [PATCH 1/4] feat: Add sample elasticsearch index prefix config. --- src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java | 9 +++++++++ .../fhir/jpa/starter/common/FhirServerConfigCommon.java | 4 ++++ src/main/resources/application.yaml | 1 + 3 files changed, 14 insertions(+) diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index 451c19dbb1b..df627f64077 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -85,6 +85,7 @@ public class AppProperties { private Boolean lastn_enabled = false; private boolean store_resource_in_lucene_index_enabled = false; + private String elasticsearch_index_prefix = ""; private NormalizedQuantitySearchLevel normalized_quantity_search_level = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED; private Boolean use_apache_address_strategy = false; @@ -560,6 +561,14 @@ public void setStore_resource_in_lucene_index_enabled(Boolean store_resource_in_ this.store_resource_in_lucene_index_enabled = store_resource_in_lucene_index_enabled; } + public String getElasticsearch_index_prefix() { + return elasticsearch_index_prefix; + } + + public void setElasticsearch_index_prefix(String elasticsearch_index_prefix) { + this.elasticsearch_index_prefix = elasticsearch_index_prefix; + } + public NormalizedQuantitySearchLevel getNormalized_quantity_search_level() { return this.normalized_quantity_search_level; } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java index 403a0a5ae23..05326289a2c 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java @@ -160,6 +160,10 @@ public JpaStorageSettings jpaStorageSettings(AppProperties appProperties) { jpaStorageSettings.setInlineResourceTextBelowSize(appProperties.getInline_resource_storage_below_size()); } + if (appProperties.getElasticsearch_index_prefix() != null && !appProperties.getElasticsearch_index_prefix().isEmpty()) { + jpaStorageSettings.setHSearchIndexPrefix(appProperties.getElasticsearch_index_prefix()); + } + jpaStorageSettings.setStoreResourceInHSearchIndex(appProperties.getStore_resource_in_lucene_index_enabled()); jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level()); jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource()); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 95ebfeca221..29486b0e506 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -220,6 +220,7 @@ hapi: # quitWait: # lastn_enabled: true # store_resource_in_lucene_index_enabled: true +# elasticsearch_index_prefix: "prefix" ### This is configuration for normalized quantity search level default is 0 ### 0: NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED - default ### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED From 02cde273c4df3c60a331050b8cdb16a252dcd743 Mon Sep 17 00:00:00 2001 From: Ming Date: Sun, 11 Feb 2024 00:06:10 +0700 Subject: [PATCH 2/4] feat: Add OAuth2 interceptor which check only expire and signature. --- pom.xml | 11 ++ .../uhn/fhir/jpa/starter/AppProperties.java | 41 +++++++ .../OAuth2AuthorizationInterceptor.java | 108 ++++++++++++++++++ src/main/resources/application.yaml | 6 +- 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java diff --git a/pom.xml b/pom.xml index 1b8fee45e9a..4b8fb9f1670 100644 --- a/pom.xml +++ b/pom.xml @@ -403,6 +403,17 @@ ${logback-classic.version} + + com.auth0 + jwks-rsa + 0.22.1 + + + com.auth0 + java-jwt + 4.4.0 + + diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index df627f64077..4ed4d46c4bc 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -95,6 +95,8 @@ public class AppProperties { private Integer bundle_batch_pool_max_size = 100; private final Set local_base_urls = new HashSet<>(); private final Set logical_urls = new HashSet<>(); + + private Oauth2 oauth2 = new Oauth2(); private final List custom_interceptor_classes = new ArrayList<>(); @@ -888,4 +890,43 @@ public boolean getEnable_index_of_type() { public void setEnable_index_of_type(boolean enable_index_of_type) { this.enable_index_of_type = enable_index_of_type; } + + public static class Oauth2 { + + public Boolean enabled = false; + public String issuer = ""; + public String jwks_uri = ""; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getJwks_uri() { + return jwks_uri; + } + + public void setJwks_uri(String jwks_uri) { + this.jwks_uri = jwks_uri; + } + } + + public Oauth2 getOauth2() { + return oauth2; + } + + public void setOauth2(Oauth2 oauth2) { + this.oauth2 = oauth2; + } } \ No newline at end of file diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java b/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java new file mode 100644 index 00000000000..68252d3f37b --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java @@ -0,0 +1,108 @@ +package ca.uhn.fhir.jpa.starter.interceptor; + +import ca.uhn.fhir.jpa.starter.AppProperties; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; +import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; +import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.JwkProviderBuilder; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.RSAKeyProvider; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.concurrent.TimeUnit; + +import java.util.List; + +@Component +public class OAuth2AuthorizationInterceptor extends AuthorizationInterceptor { + + private final AppProperties appProperties; + + public OAuth2AuthorizationInterceptor(AppProperties appProperties) { + this.appProperties = appProperties; + } + + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + if (!this.appProperties.getOauth2().getEnabled()) { + return new RuleBuilder().allowAll().build(); + } + // Get Bearer token + String authHeader = theRequestDetails.getHeader("Authorization"); + if (authHeader == null) { + return new RuleBuilder().denyAll().build(); + } + String[] authHeaders = authHeader.split(" "); + if (authHeaders.length < 2) { + return new RuleBuilder().denyAll().build(); + } + String token = authHeaders[1]; + if (validateToken(token)) { + return new RuleBuilder().allowAll().build(); + } + return new RuleBuilder().denyAll().build(); + } + + private boolean validateToken(String token) { + String jwksUrl = this.appProperties.getOauth2().getJwks_uri(); + String issuer = this.appProperties.getOauth2().getIssuer(); + try { + // Create a JwkProvider for the JWKS URL + JwkProvider provider = new JwkProviderBuilder(jwksUrl) + .cached(10, 24, TimeUnit.HOURS) // Cache up to 10 keys for 24 hours + .rateLimited(10, 1, TimeUnit.MINUTES) // Allow up to 10 requests per minute + .build(); + + Algorithm algorithm = getAlgorithm(provider); + + // Prepare the verifier with the issuer and audience if necessary + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer(issuer) + .build(); + + // Verify the token + verifier.verify(token); + + // If no exception is thrown, the token is valid + return true; + } catch (Exception e) { + // Log or handle the exception as needed + e.printStackTrace(); + return false; + } + } + + @NotNull + private static Algorithm getAlgorithm(JwkProvider provider) { + RSAKeyProvider keyProvider = new RSAKeyProvider() { + @Override + public RSAPublicKey getPublicKeyById(String keyId) { + try { + return (RSAPublicKey) provider.get(keyId).getPublicKey(); + } catch (Exception e) { + throw new RuntimeException("Could not fetch the public key from JWKS", e); + } + } + + @Override + public RSAPrivateKey getPrivateKey() { + return null; // Not used for token verification + } + + @Override + public String getPrivateKeyId() { + return null; // Not used for token verification + } + }; + + // Prepare the algorithm with the key provider + return Algorithm.RSA256(keyProvider); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 29486b0e506..af644e56ec2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -169,7 +169,7 @@ hapi: # comma-separated list of fully qualified interceptor classes. # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # or will be instantiated via reflection using an no-arg contructor; then registered with the server - #custom-interceptor-classes: + #custom-interceptor-classes: ca.uhn.fhir.jpa.starter.interceptor.OAuth2AuthorizationInterceptor # Threadpool size for BATCH'ed GETs in a bundle. # bundle_batch_pool_size: 10 @@ -226,6 +226,10 @@ hapi: ### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED ### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED # normalized_quantity_search_level: 2 +# oauth2: +# enabled: true +# issuer: "http://localhost:8080/realms/cortex" +# jwks_uri: "http://localhost:8080/realms/cortex/protocol/openid-connect/certs#" #elasticsearch: # debug: # pretty_print_json_log: false From 491aca83b168681bc67b240552fde7fba0315f1b Mon Sep 17 00:00:00 2001 From: Ming Date: Sun, 11 Feb 2024 19:40:58 +0700 Subject: [PATCH 3/4] feat: Add RBAC Supporting roles: FHIRAdmin, FHIRTerminologyAdmin and FHIRCortexReadWrite --- .../OAuth2AuthorizationInterceptor.java | 162 +++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java b/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java index 68252d3f37b..77a4ca20616 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/interceptor/OAuth2AuthorizationInterceptor.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.starter.interceptor; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.starter.AppProperties; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; @@ -10,12 +11,19 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.RSAKeyProvider; + +import org.hl7.fhir.instance.model.api.IBaseResource; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.List; @@ -23,6 +31,7 @@ @Component public class OAuth2AuthorizationInterceptor extends AuthorizationInterceptor { + private static final Logger logger = LoggerFactory.getLogger(OAuth2AuthorizationInterceptor.class); private final AppProperties appProperties; public OAuth2AuthorizationInterceptor(AppProperties appProperties) { @@ -45,10 +54,157 @@ public List buildRuleList(RequestDetails theRequestDetails) { } String token = authHeaders[1]; if (validateToken(token)) { - return new RuleBuilder().allowAll().build(); + List fhirRoles = this.getFHIRRoles(token); + if (fhirRoles == null) { + return new RuleBuilder().denyAll().build(); + } + if (fhirRoles.contains("FHIRAdmin")) { + return new RuleBuilder().allowAll().build(); + } + List rules = new ArrayList<>(); + if (fhirRoles.contains("FHIRTerminologyAdmin")) { + rules = this.addFHIRTerminologyAdminRules(rules); + } + if (fhirRoles.contains("FHIRCortexReadWrite")) { + rules = this.addFHIRCortexReadWriteRules(rules); + } + return rules; } return new RuleBuilder().denyAll().build(); - } + } + + private List getFHIRRoles(String token) { + DecodedJWT jwt = JWT.decode(token); + Map resourceAccess = jwt.getClaim("resource_access").asMap(); + if (resourceAccess != null) { + // Assuming "fhir" is the client ID and you want to access its roles + Map fhirAccess = (Map) resourceAccess.get("fhir"); + + if (fhirAccess != null) { + // Extract the roles assigned within the "fhir" client + return (List) fhirAccess.get("roles"); + } + } + return null; + } + + private List addFHIRTerminologyAdminRules(List rules) { + List newRules = new ArrayList<>(rules); + FhirVersionEnum fhirVersion = this.appProperties.getFhir_version(); + String fhirVersionName = fhirVersion.name(); + newRules.addAll(new RuleBuilder() + .allow().write().resourcesOfType("CodeSystem").withAnyId().andThen() + .allow().read().resourcesOfType("CodeSystem").withAnyId().andThen() + .allow().delete().resourcesOfType("CodeSystem").withAnyId().andThen().build() + ); + newRules.addAll(new RuleBuilder() + .allow().write().resourcesOfType("ValueSet").withAnyId().andThen() + .allow().read().resourcesOfType("ValueSet").withAnyId().andThen() + .allow().delete().resourcesOfType("ValueSet").withAnyId().andThen().build() + ); + newRules.addAll(new RuleBuilder() + .allow().write().resourcesOfType("ConceptMap").withAnyId().andThen() + .allow().read().resourcesOfType("ConceptMap").withAnyId().andThen() + .allow().delete().resourcesOfType("ConceptMap").withAnyId().andThen().build() + ); + newRules = this.addAllowReadAndAllOperationOfCodeSystem(newRules, fhirVersionName); + newRules = this.addAllowReadAndAllOperationOfValueSet(newRules, fhirVersionName); + newRules = this.addAllowReadAndAllOperationOfConceptMap(newRules, fhirVersionName); + return newRules; + } + + private List addFHIRCortexReadWriteRules(List rules) { + List newRules = new ArrayList<>(rules); + FhirVersionEnum fhirVersion = this.appProperties.getFhir_version(); + String fhirVersionName = fhirVersion.name(); + + newRules.addAll(new RuleBuilder() + .allow().write().resourcesOfType("Patient").withAnyId().andThen() + .allow().read().resourcesOfType("Patient").withAnyId().andThen() + .allow().delete().resourcesOfType("Patient").withAnyId().andThen().build() + ); + + newRules = this.addAllowReadAndAllOperationOfCodeSystem(newRules, fhirVersionName); + newRules = this.addAllowReadAndAllOperationOfValueSet(newRules, fhirVersionName); + newRules = this.addAllowReadAndAllOperationOfConceptMap(newRules, fhirVersionName); + + return newRules; + } + + private List addAllowReadAndAllOperationOfCodeSystem(List rules, String fhirVersionName) { + List newRules = new ArrayList<>(rules); + Class codeSystemClass; + switch (fhirVersionName) { + case "R4": + codeSystemClass = org.hl7.fhir.r4.model.CodeSystem.class; + break; + case "R4B": + codeSystemClass = org.hl7.fhir.r4b.model.CodeSystem.class; + break; + case "R5": + codeSystemClass = org.hl7.fhir.r5.model.CodeSystem.class; + break; + default: + newRules.addAll(new RuleBuilder().denyAll().build()); + return newRules; + } + newRules.addAll(new RuleBuilder() + .allow().read().resourcesOfType("CodeSystem").withAnyId().andThen() + .allow().operation().withAnyName().onType(codeSystemClass).andAllowAllResponses().andThen() + .build() + ); + return newRules; + } + + private List addAllowReadAndAllOperationOfValueSet(List rules, String fhirVersionName) { + List newRules = new ArrayList<>(rules); + Class valueSetClass; + switch (fhirVersionName) { + case "R4": + valueSetClass = org.hl7.fhir.r4.model.ValueSet.class; + break; + case "R4B": + valueSetClass = org.hl7.fhir.r4b.model.ValueSet.class; + break; + case "R5": + valueSetClass = org.hl7.fhir.r5.model.ValueSet.class; + break; + default: + newRules.addAll(new RuleBuilder().denyAll().build()); + return newRules; + } + newRules.addAll(new RuleBuilder() + .allow().read().resourcesOfType("ValueSet").withAnyId().andThen() + .allow().operation().withAnyName().onType(valueSetClass).andAllowAllResponses().andThen() + .build() + ); + return newRules; + } + + private List addAllowReadAndAllOperationOfConceptMap(List rules, String fhirVersionName) { + List newRules = new ArrayList<>(rules); + Class conceptMapClass; + switch (fhirVersionName) { + case "R4": + conceptMapClass = org.hl7.fhir.r4.model.ConceptMap.class; + break; + case "R4B": + conceptMapClass = org.hl7.fhir.r4b.model.ConceptMap.class; + break; + case "R5": + conceptMapClass = org.hl7.fhir.r5.model.ConceptMap.class; + break; + default: + newRules.addAll(new RuleBuilder().denyAll().build()); + return newRules; + } + newRules.addAll(new RuleBuilder() + .allow().read().resourcesOfType("ConceptMap").withAnyId().andThen() + .allow().operation().withAnyName().onType(conceptMapClass).andAllowAllResponses().andThen() + .build() + ); + return newRules; + } private boolean validateToken(String token) { String jwksUrl = this.appProperties.getOauth2().getJwks_uri(); @@ -74,7 +230,7 @@ private boolean validateToken(String token) { return true; } catch (Exception e) { // Log or handle the exception as needed - e.printStackTrace(); + logger.error(e.toString()); return false; } } From 45b846a02a5d564f4f700e3bf5c0b4e6fd2a9874 Mon Sep 17 00:00:00 2001 From: Ming Date: Mon, 12 Feb 2024 01:39:43 +0700 Subject: [PATCH 4/4] feat: Allow set config with environment variables --- Dockerfile | 7 +++- entrypoint.sh | 9 +++++ src/main/resources/application.yaml | 61 ++++++++++++++--------------- 3 files changed, 44 insertions(+), 33 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 6f03d4c0b1c..d79c01c6f96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,8 @@ COPY --from=build-hapi --chown=1001:1001 /tmp/hapi-fhir-jpaserver-starter/opente ENV ALLOW_EMPTY_PASSWORD=yes +FROM busybox:1.35.0-uclibc as busybox + ########### distroless brings focus on security and runs on plain spring boot - this is the default image FROM gcr.io/distroless/java17-debian11:nonroot AS default # 65532 is the nonroot user's uid @@ -45,5 +47,8 @@ WORKDIR /app COPY --chown=nonroot:nonroot --from=build-distroless /app /app COPY --chown=nonroot:nonroot --from=build-hapi /tmp/hapi-fhir-jpaserver-starter/opentelemetry-javaagent.jar /app +COPY --chown=nonroot:nonroot --from=busybox /bin/sh /bin/sh +COPY --chown=nonroot:nonroot --from=busybox /bin/cat /bin/cat +COPY --chown=nonroot:nonroot entrypoint.sh /entrypoint.sh -ENTRYPOINT ["java", "--class-path", "/app/main.war", "-Dloader.path=main.war!/WEB-INF/classes/,main.war!/WEB-INF/,/app/extra-classes", "org.springframework.boot.loader.PropertiesLauncher"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000000..c2e9d8ad22e --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +export ELASTICSEARCH_PASSWORD=${ELASTICSEARCH_PASSWORD:=`cat ${ELASTICSEARCH_PASSWORD_FILE}`} +export HAPI_DATASOURCE_PASSWORD=${HAPI_DATASOURCE_PASSWORD:=`cat ${HAPI_DATASOURCE_PASSWORD_FILE}`} + +# Execute the Java application +java --class-path "/app/main.war" \ +-Dloader.path="main.war!/WEB-INF/classes/,main.war!/WEB-INF/,/app/extra-classes" \ +org.springframework.boot.loader.PropertiesLauncher "$@" \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index af644e56ec2..51be58b21a1 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -14,12 +14,10 @@ spring: check-location: false baselineOnMigrate: true datasource: - url: 'jdbc:h2:file:./target/database/h2' - #url: jdbc:h2:mem:test_mem - username: sa - password: null - driverClassName: org.h2.Driver - max-active: 15 + url: ${HAPI_DATASOURCE_URL} + username: ${HAPI_DATASOURCE_USERNAME} + password: ${HAPI_DATASOURCE_PASSWORD} + driverClassName: ${HAPI_DATASOURCE_DRIVER_CLASS_NAME} # database connection pool size hikari: @@ -32,7 +30,7 @@ spring: #Hibernate dialect is automatically detected except Postgres and H2. #If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect #If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect - hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect + hibernate.dialect: ${HAPI_HIBERNATE_DIALECT} # hibernate.hbm2ddl.auto: update # hibernate.jdbc.batch_size: 20 # hibernate.cache.use_query_cache: false @@ -42,30 +40,29 @@ spring: ### These settings will enable fulltext search with lucene or elastic hibernate.search.enabled: true + hibernate.search.backend.type: elasticsearch + hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer ### lucene parameters # hibernate.search.backend.type: lucene # hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer # hibernate.search.backend.directory.type: local-filesystem # hibernate.search.backend.directory.root: target/lucenefiles # hibernate.search.backend.lucene_version: lucene_current - ### elastic parameters ===> see also elasticsearch section below <=== -# hibernate.search.backend.type: elasticsearch -# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer hapi: fhir: ### This flag when enabled to true, will avail evaluate measure operations from CR Module. ### Flag is false by default, can be passed as command line argument to override. cr: - enabled: false + enabled: "${CR_ENABLED: false}" cdshooks: - enabled: true + enabled: "${CDSHOOKS_ENABLED: false}" clientIdHeaderName: client_id ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) - openapi_enabled: true + openapi_enabled: "${OPENAPI_ENABLED: true}" ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 - fhir_version: R4 + fhir_version: ${FHIR_VERSION} ### Flag is false by default. This flag enables runtime installation of IG's. ig_runtime_upload_enabled: false ### This flag when enabled to true, will avail evaluate measure operations from CR Module. @@ -169,7 +166,7 @@ hapi: # comma-separated list of fully qualified interceptor classes. # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # or will be instantiated via reflection using an no-arg contructor; then registered with the server - #custom-interceptor-classes: ca.uhn.fhir.jpa.starter.interceptor.OAuth2AuthorizationInterceptor + custom-interceptor-classes: ca.uhn.fhir.jpa.starter.interceptor.OAuth2AuthorizationInterceptor # Threadpool size for BATCH'ed GETs in a bundle. # bundle_batch_pool_size: 10 @@ -193,7 +190,7 @@ hapi: name: Local Tester server_address: 'http://localhost:8080/fhir' refuse_to_fetch_third_party_urls: false - fhir_version: R4 + fhir_version: ${FHIR_VERSION} global: name: Global Tester server_address: "http://hapi.fhir.org/baseR4" @@ -220,24 +217,24 @@ hapi: # quitWait: # lastn_enabled: true # store_resource_in_lucene_index_enabled: true -# elasticsearch_index_prefix: "prefix" + elasticsearch_index_prefix: ${ELASTICSEARCH_INDEX_PREFIX} ### This is configuration for normalized quantity search level default is 0 ### 0: NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED - default ### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED ### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED # normalized_quantity_search_level: 2 -# oauth2: -# enabled: true -# issuer: "http://localhost:8080/realms/cortex" -# jwks_uri: "http://localhost:8080/realms/cortex/protocol/openid-connect/certs#" -#elasticsearch: -# debug: -# pretty_print_json_log: false -# refresh_after_write: false -# enabled: false -# password: SomePassword -# required_index_status: YELLOW -# rest_url: 'localhost:9200' -# protocol: 'http' -# schema_management_strategy: CREATE -# username: SomeUsername + oauth2: + enabled: "${OAUTH2_ENABLED: false}" + issuer: ${OAUTH2_ISSUER} + jwks_uri: ${OAUTH2_JWKS_URI} +elasticsearch: + enabled: "${ELASTICSEARCH_ENABLED: false}" + debug: + pretty_print_json_log: false + refresh_after_write: false + password: ${ELASTICSEARCH_PASSWORD} + required_index_status: YELLOW + rest_url: ${ELASTICSEARCH_URL} + protocol: ${ELASTICSEARCH_PROTOCOL} + schema_management_strategy: CREATE + username: ${ELASTICSEARCH_USERNAME}