mirror of
https://github.com/pcvolkmer/mv64e-etl-processor
synced 2025-09-13 09:02:50 +00:00
119 add transaction (#124)
This commit is contained in:
35
README.md
35
README.md
@@ -13,8 +13,7 @@ Duplikate werden verworfen, Änderungen werden weitergeleitet.
|
|||||||
|
|
||||||
Löschanfragen werden immer als Löschanfrage an DNPM:DIP weitergeleitet.
|
Löschanfragen werden immer als Löschanfrage an DNPM:DIP weitergeleitet.
|
||||||
|
|
||||||
Zudem ist eine minimalistische Weboberfläche integriert, die einen Einblick in den aktuellen Zustand
|
Zudem ist eine minimalistische Weboberfläche integriert, die einen Einblick in den aktuellen Zustand der Anwendung gewährt.
|
||||||
der Anwendung gewährt.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -26,6 +25,18 @@ Konfigurationsparameter
|
|||||||
|
|
||||||
### Modelvorhaben genomDE §64e
|
### Modelvorhaben genomDE §64e
|
||||||
|
|
||||||
|
#### Vorgangsummern
|
||||||
|
Zusätzlich zur Patienten Identifier Pseudonymisierung müssen Vorgangsummern generiert werden, die
|
||||||
|
jede Übertragung eindeutig identifizieren aber gleichzeitig dem Patienten zugeordnet werden können.
|
||||||
|
Dies lässt sich durch weitere Pseudonyme abbilden, allerdings werden pro Originalwert mehrere
|
||||||
|
Pseudonyme benötigt.
|
||||||
|
Zu diesem Zweck muss in gPas eine **Multi-Pseudonym-Domäne** konfiguriert werden (siehe auch
|
||||||
|
*APP_PSEUDONYMIZE_GPAS_CCDN*).
|
||||||
|
|
||||||
|
**WICHTIG:** Deaktivierte Pseudonymisierung ist nur für Tests nutzbar. Vorgangsummern sind zufällig
|
||||||
|
und werden anschließend verworfen.
|
||||||
|
|
||||||
|
#### Test Betriebsbereitschaft
|
||||||
Um die voll Betriebsbereitschaft herzustellen, muss eine erfolgreiche Übertragung mit dem
|
Um die voll Betriebsbereitschaft herzustellen, muss eine erfolgreiche Übertragung mit dem
|
||||||
Submission-Typ *Test* erfolgt sein. Über die Umgebungsvariable wird dieser Übertragungsmodus
|
Submission-Typ *Test* erfolgt sein. Über die Umgebungsvariable wird dieser Übertragungsmodus
|
||||||
aktiviert. Alle Datensätze mit erteilter Teilnahme am Modelvorhaben werden mit der Test-Kennung
|
aktiviert. Alle Datensätze mit erteilter Teilnahme am Modelvorhaben werden mit der Test-Kennung
|
||||||
@@ -98,20 +109,21 @@ vergleichbare IDs bereitzustellen.
|
|||||||
#### Eingebaute Anonymisierung
|
#### Eingebaute Anonymisierung
|
||||||
|
|
||||||
Wurde keine oder die Verwendung der eingebauten Anonymisierung konfiguriert, so wird für die
|
Wurde keine oder die Verwendung der eingebauten Anonymisierung konfiguriert, so wird für die
|
||||||
Patienten-ID der
|
Patienten-ID der entsprechende SHA-256-Hash gebildet und Base64-codiert - hier ohne endende
|
||||||
entsprechende SHA-256-Hash gebildet und Base64-codiert - hier ohne endende "=" - zuzüglich des
|
"=" - zuzüglich des konfigurierten Präfixes als Patienten-Pseudonym verwendet.
|
||||||
konfigurierten Präfixes
|
|
||||||
als Patienten-Pseudonym verwendet.
|
|
||||||
|
|
||||||
#### Pseudonymisierung mit gPAS
|
#### Pseudonymisierung mit gPAS
|
||||||
|
|
||||||
Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfigurieren.
|
Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfigurieren.
|
||||||
|
|
||||||
* `APP_PSEUDONYMIZE_GPAS_URI`: URI der gPAS-Instanz inklusive Endpoint (z.B.
|
Ab Version 2025.1 (Multi-Pseudonym Support)
|
||||||
`http://localhost:8080/ttp-fhir/fhir/gpas/$$pseudonymizeAllowCreate`)
|
|
||||||
* `APP_PSEUDONYMIZE_GPAS_TARGET`: gPas Domänenname
|
* `APP_PSEUDONYMIZE_GPAS_URI`: URI der gPAS-Instanz REST API (e.g. http://127.0.0.1:9990/ttp-fhir/fhir/gpas)
|
||||||
* `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername
|
* `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername
|
||||||
* `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort
|
* `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort
|
||||||
|
* `APP_PSEUDONYMIZE_GPAS_PID_DOMAIN`: gPas Domänenname für Patienten ID
|
||||||
|
* `APP_PSEUDONYMIZE_GPAS_GENOM_DE_TAN_DOMAIN`: gPas Multi-Pseudonym-Domäne für genomDE Vorgangsnummern (
|
||||||
|
Clinical data node)
|
||||||
|
|
||||||
### (Externe) Consent-Services
|
### (Externe) Consent-Services
|
||||||
|
|
||||||
@@ -173,8 +185,7 @@ Modelvorhaben §64e.
|
|||||||
### Anmeldung mit einem Passwort
|
### Anmeldung mit einem Passwort
|
||||||
|
|
||||||
Ein initialer Administrator-Account kann optional konfiguriert werden und sorgt dafür, dass
|
Ein initialer Administrator-Account kann optional konfiguriert werden und sorgt dafür, dass
|
||||||
bestimmte Bereiche nur nach
|
bestimmte Bereiche nur nach einem erfolgreichen Login erreichbar sind.
|
||||||
einem erfolgreichen Login erreichbar sind.
|
|
||||||
|
|
||||||
* `APP_SECURITY_ADMIN_USER`: Muss angegeben werden zur Aktivierung der Zugriffsbeschränkung.
|
* `APP_SECURITY_ADMIN_USER`: Muss angegeben werden zur Aktivierung der Zugriffsbeschränkung.
|
||||||
* `APP_SECURITY_ADMIN_PASSWORD`: Das Passwort für den Administrator (Empfohlen).
|
* `APP_SECURITY_ADMIN_PASSWORD`: Das Passwort für den Administrator (Empfohlen).
|
||||||
|
@@ -43,14 +43,14 @@ class GpasPseudonymGeneratorTest {
|
|||||||
private lateinit var mockRestServiceServer: MockRestServiceServer
|
private lateinit var mockRestServiceServer: MockRestServiceServer
|
||||||
private lateinit var generator: GpasPseudonymGenerator
|
private lateinit var generator: GpasPseudonymGenerator
|
||||||
private lateinit var restTemplate: RestTemplate
|
private lateinit var restTemplate: RestTemplate
|
||||||
private var appFhirConfig: AppFhirConfig = AppFhirConfig()
|
private var appFhirConfig: AppFhirConfig = AppFhirConfig()
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build()
|
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build()
|
||||||
val gPasConfigProperties = GPasConfigProperties(
|
val gPasConfigProperties = GPasConfigProperties(
|
||||||
"https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate",
|
"https://localhost:9990/ttp-fhir/fhir/gpas",
|
||||||
"test",
|
"test", "test2",
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
@@ -23,4 +23,6 @@ public interface Generator {
|
|||||||
|
|
||||||
String generate(String id);
|
String generate(String id);
|
||||||
|
|
||||||
|
String generateGenomDeTan(String id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,11 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
||||||
import dev.dnpm.etl.processor.config.GPasConfigProperties;
|
import dev.dnpm.etl.processor.config.GPasConfigProperties;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.hc.core5.net.URIBuilder;
|
||||||
import org.hl7.fhir.r4.model.Identifier;
|
import org.hl7.fhir.r4.model.Identifier;
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
||||||
@@ -42,38 +46,67 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
|
|
||||||
private final FhirContext r4Context;
|
private final FhirContext r4Context;
|
||||||
private final String gPasUrl;
|
private final String gPasUrl;
|
||||||
private final String psnTargetDomain;
|
|
||||||
private final HttpHeaders httpHeader;
|
private final HttpHeaders httpHeader;
|
||||||
private final RetryTemplate retryTemplate;
|
private final RetryTemplate retryTemplate;
|
||||||
private final Logger log = LoggerFactory.getLogger(GpasPseudonymGenerator.class);
|
private final Logger log = LoggerFactory.getLogger(GpasPseudonymGenerator.class);
|
||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
private final @NotNull String genomDeTanDomain;
|
||||||
|
private final @NotNull String pidPsnDomain;
|
||||||
|
protected final static String createOrGetPsn = "$pseudonymizeAllowCreate";
|
||||||
|
protected final static String createMultiDomainPsn = "$pseudonymize-secondary";
|
||||||
|
private final static String SINGLE_PSN_PART_NAME = "pseudonym";
|
||||||
|
private final static String MULTI_PSN_PART_NAME = "value";
|
||||||
|
|
||||||
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate,
|
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate,
|
||||||
RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
||||||
this.retryTemplate = retryTemplate;
|
this.retryTemplate = retryTemplate;
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
this.gPasUrl = gpasCfg.getUri();
|
this.gPasUrl = gpasCfg.getUri();
|
||||||
this.psnTargetDomain = gpasCfg.getTarget();
|
this.pidPsnDomain = gpasCfg.getPatientDomain();
|
||||||
|
this.genomDeTanDomain = gpasCfg.getGenomDeTanDomain();
|
||||||
this.r4Context = appFhirConfig.fhirContext();
|
this.r4Context = appFhirConfig.fhirContext();
|
||||||
httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
|
httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
|
||||||
|
|
||||||
log.debug(String.format("%s has been initialized", this.getClass().getName()));
|
log.debug("{} has been initialized", this.getClass().getName());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generate(String id) {
|
public String generate(String id) {
|
||||||
var gPasRequestBody = getGpasRequestBody(id);
|
return generate(id, PsnDomainType.SINGLE_PSN_DOMAIN);
|
||||||
var responseEntity = getGpasPseudonym(gPasRequestBody);
|
}
|
||||||
var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
|
|
||||||
.parseResource(responseEntity.getBody());
|
|
||||||
|
|
||||||
return unwrapPseudonym(gPasPseudonymResult);
|
@Override
|
||||||
|
public String generateGenomDeTan(String id) {
|
||||||
|
return generate(id, PsnDomainType.MULTI_PSN_DOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String generate(String id, PsnDomainType domainType) {
|
||||||
|
switch (domainType) {
|
||||||
|
case SINGLE_PSN_DOMAIN -> {
|
||||||
|
final var requestBody = createSinglePsnRequestBody(id, pidPsnDomain);
|
||||||
|
final var responseEntity = getGpasPseudonym(requestBody, createOrGetPsn);
|
||||||
|
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
|
||||||
|
.parseResource(responseEntity.getBody());
|
||||||
|
|
||||||
|
return unwrapPseudonym(gPasPseudonymResult, SINGLE_PSN_PART_NAME);
|
||||||
|
}
|
||||||
|
case MULTI_PSN_DOMAIN -> {
|
||||||
|
final var requestBody = createMultiPsnRequestBody(id, genomDeTanDomain);
|
||||||
|
final var responseEntity = getGpasPseudonym(requestBody, createMultiDomainPsn);
|
||||||
|
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
|
||||||
|
.parseResource(responseEntity.getBody());
|
||||||
|
|
||||||
|
return unwrapPseudonym(gPasPseudonymResult, MULTI_PSN_PART_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NotImplementedException(
|
||||||
|
"give domain type '%s' is unexpected and is currently not supported!".formatted(
|
||||||
|
domainType));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static String unwrapPseudonym(Parameters gPasPseudonymResult) {
|
public static String unwrapPseudonym(Parameters gPasPseudonymResult, String targetPartName) {
|
||||||
final var parameters = gPasPseudonymResult.getParameter().stream().findFirst();
|
final var parameters = gPasPseudonymResult.getParameter().stream().findFirst();
|
||||||
|
|
||||||
if (parameters.isEmpty()) {
|
if (parameters.isEmpty()) {
|
||||||
@@ -81,7 +114,7 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final var identifier = (Identifier) parameters.get().getPart().stream()
|
final var identifier = (Identifier) parameters.get().getPart().stream()
|
||||||
.filter(a -> a.getName().equals("pseudonym"))
|
.filter(a -> a.getName().equals(targetPartName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseGet(ParametersParameterComponent::new).getValue();
|
.orElseGet(ParametersParameterComponent::new).getValue();
|
||||||
|
|
||||||
@@ -104,13 +137,14 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
protected ResponseEntity<String> getGpasPseudonym(String gPasRequestBody) {
|
protected ResponseEntity<String> getGpasPseudonym(String gPasRequestBody, String apiEndpoint) {
|
||||||
|
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(gPasRequestBody, this.httpHeader);
|
HttpEntity<String> requestEntity = new HttpEntity<>(gPasRequestBody, this.httpHeader);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
var targetUrl = buildRequestUrl(apiEndpoint);
|
||||||
ResponseEntity<String> responseEntity = retryTemplate.execute(
|
ResponseEntity<String> responseEntity = retryTemplate.execute(
|
||||||
ctx -> restTemplate.exchange(gPasUrl, HttpMethod.POST, requestEntity,
|
ctx -> restTemplate.exchange(targetUrl, HttpMethod.POST, requestEntity,
|
||||||
String.class));
|
String.class));
|
||||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||||
log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
|
log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
|
||||||
@@ -139,16 +173,43 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getGpasRequestBody(String id) {
|
protected URI buildRequestUrl(String apiEndpoint) throws URISyntaxException {
|
||||||
var requestParameters = new Parameters();
|
var gPasUrl1 = gPasUrl;
|
||||||
|
if (gPasUrl.lastIndexOf("/") == gPasUrl.length() - 1) {
|
||||||
|
gPasUrl1 = gPasUrl.substring(0, gPasUrl.length() - 1);
|
||||||
|
}
|
||||||
|
var urlBuilder = new URIBuilder(new URI(gPasUrl1)).appendPath(apiEndpoint);
|
||||||
|
|
||||||
|
return urlBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String createSinglePsnRequestBody(String id, String targetDomain) {
|
||||||
|
final var requestParameters = new Parameters();
|
||||||
requestParameters.addParameter().setName("target")
|
requestParameters.addParameter().setName("target")
|
||||||
.setValue(new StringType().setValue(psnTargetDomain));
|
.setValue(new StringType().setValue(targetDomain));
|
||||||
requestParameters.addParameter().setName("original")
|
requestParameters.addParameter().setName("original")
|
||||||
.setValue(new StringType().setValue(id));
|
.setValue(new StringType().setValue(id));
|
||||||
final IParser iParser = r4Context.newJsonParser();
|
final IParser iParser = r4Context.newJsonParser();
|
||||||
return iParser.encodeResourceToString(requestParameters);
|
return iParser.encodeResourceToString(requestParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String createMultiPsnRequestBody(String id, String targetDomain) {
|
||||||
|
final var param = new Parameters();
|
||||||
|
ParametersParameterComponent targetParam = param.addParameter().setName("original");
|
||||||
|
targetParam.addPart(
|
||||||
|
new ParametersParameterComponent().setName("target")
|
||||||
|
.setValue(new StringType(targetDomain)));
|
||||||
|
targetParam.addPart(
|
||||||
|
new ParametersParameterComponent().setName("value").setValue(new StringType(id)));
|
||||||
|
targetParam
|
||||||
|
.addPart(new ParametersParameterComponent().setName("count").setValue(
|
||||||
|
new StringType("1")));
|
||||||
|
|
||||||
|
final IParser iParser = r4Context.newJsonParser();
|
||||||
|
return iParser.encodeResourceToString(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
protected HttpHeaders getHttpHeaders(String gPasUserName, String gPasPassword) {
|
protected HttpHeaders getHttpHeaders(String gPasUserName, String gPasPassword) {
|
||||||
var headers = new HttpHeaders();
|
var headers = new HttpHeaders();
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.dnpm.etl.processor.pseudonym;
|
||||||
|
|
||||||
|
public enum PsnDomainType {
|
||||||
|
/**
|
||||||
|
* one pseudonym per original value
|
||||||
|
*/
|
||||||
|
SINGLE_PSN_DOMAIN,
|
||||||
|
/**
|
||||||
|
* multiple pseudonymes for one original value
|
||||||
|
*/
|
||||||
|
MULTI_PSN_DOMAIN
|
||||||
|
}
|
@@ -48,7 +48,8 @@ data class PseudonymizeConfigProperties(
|
|||||||
@ConfigurationProperties(GPasConfigProperties.NAME)
|
@ConfigurationProperties(GPasConfigProperties.NAME)
|
||||||
data class GPasConfigProperties(
|
data class GPasConfigProperties(
|
||||||
val uri: String?,
|
val uri: String?,
|
||||||
val target: String = "etl-processor",
|
val patientDomain: String = "etl-processor",
|
||||||
|
val genomDeTanDomain: String = "ccdn",
|
||||||
val username: String?,
|
val username: String?,
|
||||||
val password: String?,
|
val password: String?,
|
||||||
) {
|
) {
|
||||||
|
@@ -21,9 +21,12 @@ package dev.dnpm.etl.processor.pseudonym
|
|||||||
|
|
||||||
import org.apache.commons.codec.binary.Base32
|
import org.apache.commons.codec.binary.Base32
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
class AnonymizingGenerator : Generator {
|
class AnonymizingGenerator : Generator {
|
||||||
|
companion object fun getSecureRandom() : SecureRandom {
|
||||||
|
return SecureRandom()
|
||||||
|
}
|
||||||
|
|
||||||
override fun generate(id: String): String {
|
override fun generate(id: String): String {
|
||||||
return Base32().encodeAsString(DigestUtils.sha256(id))
|
return Base32().encodeAsString(DigestUtils.sha256(id))
|
||||||
@@ -31,4 +34,14 @@ class AnonymizingGenerator : Generator {
|
|||||||
.lowercase()
|
.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
override fun generateGenomDeTan(id: String?): String {
|
||||||
|
|
||||||
|
val bytes = ByteArray(64 / 2)
|
||||||
|
getSecureRandom().nextBytes(bytes)
|
||||||
|
|
||||||
|
return bytes.joinToString("") { "%02x".format(it) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -35,6 +35,10 @@ class PseudonymizeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun genomDeTan(patientId: PatientId): String {
|
||||||
|
return generator.generateGenomDeTan(patientId.value)
|
||||||
|
}
|
||||||
|
|
||||||
fun prefix(): String {
|
fun prefix(): String {
|
||||||
return configProperties.prefix
|
return configProperties.prefix
|
||||||
}
|
}
|
||||||
|
@@ -349,3 +349,8 @@ fun Mtb.ensureMetaDataIsInitialized() {
|
|||||||
this.metadata.modelProjectConsent.provisions.toMutableList()
|
this.metadata.modelProjectConsent.provisions.toMutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix fun Mtb.addGenomDeTan(pseudonymizeService: PseudonymizeService)
|
||||||
|
{
|
||||||
|
this.metadata.transferTan = pseudonymizeService.genomDeTan(PatientId(this.patient.id))
|
||||||
|
}
|
@@ -38,7 +38,7 @@ class ConsentProcessor(
|
|||||||
/**
|
/**
|
||||||
* In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
|
* In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
|
||||||
*
|
*
|
||||||
* Logik:
|
* Logic:
|
||||||
* * <c>true</c> IF consent check is disabled.
|
* * <c>true</c> IF consent check is disabled.
|
||||||
* * <c>true</c> IF broad consent (BC) has been given.
|
* * <c>true</c> IF broad consent (BC) has been given.
|
||||||
* * <c>true</c> BC has been asked AND declined but genomDe consent has been consented.
|
* * <c>true</c> BC has been asked AND declined but genomDe consent has been consented.
|
||||||
|
@@ -30,8 +30,11 @@ import dev.dnpm.etl.processor.monitoring.RequestStatus
|
|||||||
import dev.dnpm.etl.processor.monitoring.RequestType
|
import dev.dnpm.etl.processor.monitoring.RequestType
|
||||||
import dev.dnpm.etl.processor.output.*
|
import dev.dnpm.etl.processor.output.*
|
||||||
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
||||||
|
import dev.dnpm.etl.processor.pseudonym.addGenomDeTan
|
||||||
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
||||||
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import org.apache.commons.codec.binary.Base32
|
import org.apache.commons.codec.binary.Base32
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
@@ -76,9 +79,12 @@ class RequestProcessor(
|
|||||||
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
||||||
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
||||||
|
|
||||||
val isConsentOk = consentProcessor != null &&
|
val isConsentOk =
|
||||||
consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null
|
consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null
|
||||||
if (isConsentOk) {
|
if (isConsentOk) {
|
||||||
|
if (isGenomDeConsented(mtbFile)) {
|
||||||
|
mtbFile addGenomDeTan pseudonymizeService
|
||||||
|
}
|
||||||
mtbFile pseudonymizeWith pseudonymizeService
|
mtbFile pseudonymizeWith pseudonymizeService
|
||||||
mtbFile anonymizeContentWith pseudonymizeService
|
mtbFile anonymizeContentWith pseudonymizeService
|
||||||
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
||||||
@@ -93,6 +99,13 @@ class RequestProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isGenomDeConsented(mtbFile: Mtb): Boolean {
|
||||||
|
val isModelProjectConsented = mtbFile.metadata?.modelProjectConsent?.provisions?.any { p ->
|
||||||
|
p.purpose == ModelProjectConsentPurpose.SEQUENCING && p.type == ConsentProvision.PERMIT
|
||||||
|
} == true
|
||||||
|
return isModelProjectConsented
|
||||||
|
}
|
||||||
|
|
||||||
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
|
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
|
||||||
requestService.save(
|
requestService.save(
|
||||||
Request(
|
Request(
|
||||||
|
@@ -71,8 +71,8 @@ class PseudonymizeServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun sanitizeFileName(@Mock generator: GpasPseudonymGenerator) {
|
fun sanitizeFileName() {
|
||||||
val result= GpasPseudonymGenerator.sanitizeValue("l://a\\bs;1*2?3>")
|
val result = GpasPseudonymGenerator.sanitizeValue("l://a\\bs;1*2?3>")
|
||||||
|
|
||||||
assertThat(result).isEqualTo("l___a_bs_1_2_3_")
|
assertThat(result).isEqualTo("l___a_bs_1_2_3_")
|
||||||
}
|
}
|
||||||
@@ -90,4 +90,16 @@ class PseudonymizeServiceTest {
|
|||||||
assertThat(mtbFile.patient.id).isEqualTo("UNKNOWN_123")
|
assertThat(mtbFile.patient.id).isEqualTo("UNKNOWN_123")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldReturnDifferentValues() {
|
||||||
|
val ag = AnonymizingGenerator()
|
||||||
|
|
||||||
|
val tans = HashSet<String>()
|
||||||
|
|
||||||
|
(1..1000).forEach { i ->
|
||||||
|
val tan = ag.generateGenomDeTan("12345")
|
||||||
|
assertThat(tan).hasSize(64)
|
||||||
|
assertThat(tans.add(tan)).`as`("never the same result!").isTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user