diff --git a/README.md b/README.md index e469da8a..fba4722e 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,17 @@ To configure the location of the external service, use the following parameters. | ELASTIC_SEARCH_HOST | Host and port of the elastic search endpoint | `192.168.1.1:9200` | `localhost:9200` | | ELASTIC_SEARCH_FILTER | Which parameters can be used to filter results | `foo,bar,baz` | `context,terminology,kds_module` | +### Passthrough Variables + +There are a few variables that are not used in the backend itself, but are forwarded to the frontend if requested. + + +| EnvVar | Description | Example | Default | +|-----------------------------|------------------------------------------------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------| +| PT_CCDL_VERSION | The used version of the Clinical Cohort Definition Language | `` | `unknown` | +| PT_PORTAL_LINK | URL to the portal page | `https://foo.bar` | `https://antrag.forschen-fuer-gesundheit.de` | +| PT_DSE_PATIENT_PROFILE_URL | URL of the patient profile used in data selection and extraction | `foo,bar,baz` | `https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/PatientPseudonymisiert` | + ## Support for self-signed certificates The dataportal backend supports the use of self-signed certificates from your own CAs. diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java b/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java index 115995e3..a064995d 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java @@ -51,6 +51,7 @@ public class WebSecurityConfig { public static final String PATH_CODEABLE_CONCEPT = "/codeable-concept"; public static final String PATH_SWAGGER_UI = "/swagger-ui/**"; public static final String PATH_SWAGGER_CONFIG = "/v3/api-docs/**"; + public static final String PATH_SETTINGS = "/.settings"; @Value("${app.keycloakAllowedRole}") private String keycloakAllowedRole; @@ -97,6 +98,7 @@ public SecurityFilterChain apiFilterChain( Converter authenticationConverter) throws Exception { http.authorizeHttpRequests(authorize -> authorize + .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(PATH_SETTINGS)).permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(PATH_SWAGGER_CONFIG)).permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(PATH_API + PATH_ACTUATOR_HEALTH)).permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(PATH_API + PATH_ACTUATOR_INFO)).permitAll() diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/settings/SettingsController.java b/src/main/java/de/numcodex/feasibility_gui_backend/settings/SettingsController.java new file mode 100644 index 00000000..d3939e2a --- /dev/null +++ b/src/main/java/de/numcodex/feasibility_gui_backend/settings/SettingsController.java @@ -0,0 +1,60 @@ +package de.numcodex.feasibility_gui_backend.settings; + +import java.util.Map; + +import de.numcodex.feasibility_gui_backend.config.WebSecurityConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@CrossOrigin(origins = "${cors.allowedOrigins}", exposedHeaders = "Location") +public class SettingsController { + + private static final String KEY_DSE_URL = "dsePatientProfileUrl"; + private static final String KEY_LOCATION_RESULT_LOWERBOUNDARY = "lowerboundarylocationresult"; + private static final String KEY_PATIENT_RESULT_LOWERBOUNDARY = "lowerboundarypatientresult"; + private static final String KEY_POLLING_INTERVAL = "pollingintervall"; + private static final String KEY_POLLING_TIME = "pollingtime"; + private static final String KEY_PORTAL_LINK = "proposalPortalLink"; + private static final String KEY_CCDL_VERSION = "ccdlVersion"; + private static final String KEY_REST_API_PATH = "uiBackendApiUrl"; + + + @Value("${passthrough.dsePatientProfileUrl}") + private String dseUrl; + + @Value("${app.privacy.threshold.sites}") + private int lowerboundaryLocationResult; + + @Value("${app.privacy.threshold.results}") + private int lowerboundardyPatientResult; + + @Value("${app.privacy.quota.read.resultDetailedObfuscated.interval}") + private String pollingInterval; + + @Value("${app.privacy.quota.read.resultSummary.pollingInterval}") + private String pollingTime = "PT20S"; + + @Value("${passthrough.portalLink}") + private String portalLink; + + @Value("${passthrough.ccdlVersion}") + private String ccdlVersion = "version"; + + + @GetMapping("/.settings") + public Map getSettings() { + return Map.of( + KEY_DSE_URL, dseUrl, + KEY_LOCATION_RESULT_LOWERBOUNDARY, lowerboundaryLocationResult, + KEY_PATIENT_RESULT_LOWERBOUNDARY, lowerboundardyPatientResult, + KEY_POLLING_INTERVAL, pollingInterval, + KEY_POLLING_TIME, pollingTime, + KEY_PORTAL_LINK, portalLink, + KEY_CCDL_VERSION, ccdlVersion, + KEY_REST_API_PATH, WebSecurityConfig.PATH_API + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7fe45f13..8524bfb6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -149,7 +149,10 @@ app: pollingInterval: ${PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_POLLINGINTERVAL:PT10S} amount: ${PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_AMOUNT:3} interval: ${PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_INTERVAL:PT2H} - +passthrough: + ccdlVersion: ${PT_CCDL_VERSION:unknown} + portalLink: ${PT_PORTAL_LINK:https://antrag.forschen-fuer-gesundheit.de} + dsePatientProfileUrl: ${PT_DSE_PATIENT_PROFILE_URL:https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/PatientPseudonymisiert} logging: level: org.hibernate: ${LOG_LEVEL_SQL:warn} diff --git a/src/main/resources/static/v3/api-docs/swagger.yaml b/src/main/resources/static/v3/api-docs/swagger.yaml index cac1e89f..5458d6c2 100644 --- a/src/main/resources/static/v3/api-docs/swagger.yaml +++ b/src/main/resources/static/v3/api-docs/swagger.yaml @@ -869,13 +869,27 @@ paths: operationId: '' responses: 200: - description: Successful health information. + description: Successful build information. content: application/vnd.spring-boot.actuator.v3+json: schema: $ref: "#/components/schemas/SystemInfo" tags: - intrinsics + /.settings: + get: + summary: Offers config settings needed by the frontend. + description: '' + operationId: '' + responses: + 200: + description: Successful health information. + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsInfo" + tags: + - intrinsics components: schemas: Dataquery: @@ -1668,6 +1682,43 @@ components: $ref: "#/components/schemas/BuildInfo" terminology: $ref: "#/components/schemas/Terminology" + SettingsInfo: + type: object + properties: + ccdlVersion: + type: string + examples: + - v1 + pollingintervall: + type: string + format: duration + examples: + - PT10S + dsePatientProfileUrl: + type: string + examples: + - https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/PatientPseudonymisiert + uiBackendApiUrl: + type: string + examples: + - /api/v5 + lowerboundarypatientresult: + type: number + examples: + - 20 + proposalPortalLink: + type: string + examples: + - https://antrag.forschen-fuer-gesundheit.de + lowerboundarylocationresult: + type: number + examples: + - 3 + pollingtime: + type: string + format: duration + examples: + - PT10S securitySchemes: dataportal_auth: type: oauth2 diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/settings/SettingsControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/settings/SettingsControllerIT.java new file mode 100644 index 00000000..317299ce --- /dev/null +++ b/src/test/java/de/numcodex/feasibility_gui_backend/settings/SettingsControllerIT.java @@ -0,0 +1,41 @@ +package de.numcodex.feasibility_gui_backend.settings; + +import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingInterceptor; +import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingServiceSpringConfig; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import java.net.URI; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Tag("info") +@ExtendWith(SpringExtension.class) +@Import(RateLimitingServiceSpringConfig.class) +@WebMvcTest( + controllers = SettingsController.class +) +class SettingsControllerIT { + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private RateLimitingInterceptor rateLimitingInterceptor; + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + void getSettings_succeeds() throws Exception { + mockMvc.perform(get(URI.create("/.settings")).with(csrf())) + .andExpect(status().isOk()); + } +} \ No newline at end of file