Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 13093 data protection update #13170

Merged
merged 7 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ public class CaseDataDto extends SormasToSormasShareableDto implements IsCase {
@Valid
@EmbeddedPersonalData
@EmbeddedSensitiveData
@SensitiveData
leventegal-she marked this conversation as resolved.
Show resolved Hide resolved
private HealthConditionsDto healthConditions;

private YesNoUnknown pregnant;
Expand Down Expand Up @@ -513,7 +514,9 @@ public class CaseDataDto extends SormasToSormasShareableDto implements IsCase {
COUNTRY_CODE_GERMANY,
COUNTRY_CODE_SWITZERLAND })
private Date quarantineOfficialOrderSentDate;
@SensitiveData
private YesNoUnknown postpartum;
@SensitiveData
private Trimester trimester;
private FollowUpStatus followUpStatus;
@SensitiveData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ public class CaseExportDto extends AbstractUuidDto implements IsCase {
private String vaccineBatchNumber;
private String vaccineUniiCode;
private String vaccineAtcCode;
@SensitiveData
private HealthConditionsDto healthConditions;
private int numberOfPrescriptions;
private int numberOfTreatments;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ public class ContactDto extends SormasToSormasShareableDto implements IsContact
@Valid
private EpiDataDto epiData;
@Valid
@SensitiveData
private HealthConditionsDto healthConditions;
private YesNoUnknown returningTraveler;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,16 @@ public class EventDto extends SormasToSormasShareableDto {
private InstitutionalPartnerType srcInstitutionalPartnerType;
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String srcInstitutionalPartnerTypeDetails;
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String srcFirstName;
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String srcLastName;
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String srcTelNo;
@SensitiveData
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String srcEmail;
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
Expand Down
13 changes: 13 additions & 0 deletions sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,19 @@ public interface Captions {
String edit = "edit";
String endDateTime = "endDateTime";
String endOfProcessingDate = "endOfProcessingDate";
String EntityColumn_CAPTION = "EntityColumn.CAPTION";
String EntityColumn_DATA_PROTECTION = "EntityColumn.DATA_PROTECTION";
String EntityColumn_DESCRIPTION = "EntityColumn.DESCRIPTION";
String EntityColumn_DISEASES = "EntityColumn.DISEASES";
String EntityColumn_ENTITY = "EntityColumn.ENTITY";
String EntityColumn_EXCLUSIVE_COUNTRIES = "EntityColumn.EXCLUSIVE_COUNTRIES";
String EntityColumn_FIELD = "EntityColumn.FIELD";
String EntityColumn_FIELD_ID = "EntityColumn.FIELD_ID";
String EntityColumn_IGNORED_COUNTRIES = "EntityColumn.IGNORED_COUNTRIES";
String EntityColumn_NEW_DISEASE = "EntityColumn.NEW_DISEASE";
String EntityColumn_OUTBREAKS = "EntityColumn.OUTBREAKS";
String EntityColumn_REQUIRED = "EntityColumn.REQUIRED";
String EntityColumn_TYPE = "EntityColumn.TYPE";
String Environment = "Environment";
String Environment_description = "Environment.description";
String Environment_environmentMedia = "Environment.environmentMedia";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,7 @@ public interface Strings {
String messageCountImmunizationsNotRestored = "messageCountImmunizationsNotRestored";
String messageCountriesArchived = "messageCountriesArchived";
String messageCountriesDearchived = "messageCountriesDearchived";
String messageCountriesExcludedFromDataProtection = "messageCountriesExcludedFromDataProtection";
String messageCountryArchived = "messageCountryArchived";
String messageCountryDearchived = "messageCountryDearchived";
String messageCountryDearchivingNotPossible = "messageCountryDearchivingNotPossible";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ public class LocationDto extends PseudonymizableDto {
CountryHelper.COUNTRY_CODE_GERMANY,
CountryHelper.COUNTRY_CODE_FRANCE })
private String details;
@PersonalData
@SensitiveData
@PersonalData(excludeForCountries = {
CountryHelper.COUNTRY_CODE_LUXEMBOURG })
@SensitiveData(excludeForCountries = {
CountryHelper.COUNTRY_CODE_LUXEMBOURG })
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
private String city;
@PersonalData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@
public @interface PersonalData {

boolean mandatoryField() default false;

String[] excludeForCountries() default {};
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
/*
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
* Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package de.symeda.sormas.api.utils;
Expand All @@ -28,4 +25,6 @@
public @interface SensitiveData {

boolean mandatoryField() default false;

String[] excludeForCountries() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,44 @@ public static <T> UiFieldAccessCheckers<T> getNoop() {
return new UiFieldAccessCheckers<>();
}

public static <T> UiFieldAccessCheckers<T> getDefault(boolean isPseudonymized) {
public static <T> UiFieldAccessCheckers<T> getDefault(boolean isPseudonymized, String serverCountry) {
UiFieldAccessCheckers<T> fieldAccessCheckers = new UiFieldAccessCheckers<>();

fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forPersonalData(isPseudonymized))
.add(PseudonymizedFieldAccessChecker.forSensitiveData(isPseudonymized));
fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forPersonalData(isPseudonymized, serverCountry))
.add(PseudonymizedFieldAccessChecker.forSensitiveData(isPseudonymized, serverCountry));

return fieldAccessCheckers;
}

public static <T> UiFieldAccessCheckers<T> forPersonalData(boolean isPseudonymized) {
public static <T> UiFieldAccessCheckers<T> forPersonalData(boolean isPseudonymized, String serverCountry) {
UiFieldAccessCheckers<T> fieldAccessCheckers = new UiFieldAccessCheckers<>();

fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forPersonalData(isPseudonymized));
fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forPersonalData(isPseudonymized, serverCountry));

return fieldAccessCheckers;
}

public static <T> UiFieldAccessCheckers<T> forSensitiveData(boolean isPseudonymized) {
public static <T> UiFieldAccessCheckers<T> forSensitiveData(boolean isPseudonymized, String serverCountry) {
UiFieldAccessCheckers<T> fieldAccessCheckers = new UiFieldAccessCheckers<>();

fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forSensitiveData(isPseudonymized));
fieldAccessCheckers.add(PseudonymizedFieldAccessChecker.forSensitiveData(isPseudonymized, serverCountry));

return fieldAccessCheckers;
}

public static <T> UiFieldAccessCheckers<T> forDataAccessLevel(PseudonymizableDataAccessLevel accessLevel, boolean isPseudonymized) {
public static <T> UiFieldAccessCheckers<T> forDataAccessLevel(
PseudonymizableDataAccessLevel accessLevel,
boolean isPseudonymized,
String serverCountry) {

switch (accessLevel) {
case ALL:
case NONE:
return getDefault(isPseudonymized);
return getDefault(isPseudonymized, serverCountry);
case PERSONAL:
return forSensitiveData(isPseudonymized);
return forSensitiveData(isPseudonymized, serverCountry);
case SENSITIVE:
return forPersonalData(isPseudonymized);
return forPersonalData(isPseudonymized, serverCountry);
default:
throw new IllegalArgumentException(accessLevel.name());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public abstract class AnnotationBasedFieldAccessChecker<T> implements FieldAccessChecker<T> {

private final Class<? extends Annotation> fieldAnnotation;
protected final Class<? extends Annotation> fieldAnnotation;
private final Class<? extends Annotation> embeddedAnnotation;
private final boolean hasRight;
private final SpecialAccessCheck<T> specialAccessCheck;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,64 @@
package de.symeda.sormas.api.utils.fieldaccess.checkers;

import java.lang.reflect.Field;
import java.util.Arrays;

import de.symeda.sormas.api.user.UserRight;
import de.symeda.sormas.api.utils.EmbeddedPersonalData;
import de.symeda.sormas.api.utils.PersonalData;

public final class PersonalDataFieldAccessChecker<T> extends AnnotationBasedFieldAccessChecker<T> {

private PersonalDataFieldAccessChecker(final boolean hasRight, SpecialAccessCheck<T> specialAccessCheck) {
private final String serverCountry;

private PersonalDataFieldAccessChecker(final boolean hasRight, SpecialAccessCheck<T> specialAccessCheck, String serverCountry) {
super(PersonalData.class, EmbeddedPersonalData.class, hasRight, specialAccessCheck);
this.serverCountry = serverCountry;
}

public static <T> PersonalDataFieldAccessChecker<T> inJurisdiction(RightCheck rightCheck, SpecialAccessCheck<T> specialAccessCheck) {
return new PersonalDataFieldAccessChecker<>(rightCheck.check(UserRight.SEE_PERSONAL_DATA_IN_JURISDICTION), specialAccessCheck);
public static <T> PersonalDataFieldAccessChecker<T> inJurisdiction(
RightCheck rightCheck,
SpecialAccessCheck<T> specialAccessCheck,
String serverCountry) {
return new PersonalDataFieldAccessChecker<>(rightCheck.check(UserRight.SEE_PERSONAL_DATA_IN_JURISDICTION), specialAccessCheck, serverCountry);
}

public static <T> PersonalDataFieldAccessChecker<T> outsideJurisdiction(RightCheck rightCheck, SpecialAccessCheck<T> specialAccessCheck) {
return new PersonalDataFieldAccessChecker<>(rightCheck.check(UserRight.SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION), specialAccessCheck);
public static <T> PersonalDataFieldAccessChecker<T> outsideJurisdiction(
RightCheck rightCheck,
SpecialAccessCheck<T> specialAccessCheck,
String serverCountry) {
return new PersonalDataFieldAccessChecker<>(
rightCheck.check(UserRight.SEE_PERSONAL_DATA_OUTSIDE_JURISDICTION),
specialAccessCheck,
serverCountry);
}

public static <T> PersonalDataFieldAccessChecker<T> forcedNoAccess() {
return new PersonalDataFieldAccessChecker<>(false, t -> false);
return new PersonalDataFieldAccessChecker<>(false, t -> false, null);
}

@Override
protected boolean isAnnotatedFieldMandatory(Field annotatedField) {
return annotatedField.getAnnotation(PersonalData.class).mandatoryField();
}

@Override
public boolean isConfiguredForCheck(Field field, boolean withMandatory) {
boolean annotationPresent = field.isAnnotationPresent(fieldAnnotation);

if (annotationPresent) {
String[] excludeForCountries = field.getAnnotation(PersonalData.class).excludeForCountries();
if (Arrays.asList(excludeForCountries).contains(serverCountry)) {
return false;
}
}

if (!annotationPresent || withMandatory) {
return annotationPresent;
}
return !isAnnotatedFieldMandatory(field);
}

public interface RightCheck {

boolean check(UserRight userRight);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;

import de.symeda.sormas.api.utils.EmbeddedPersonalData;
import de.symeda.sormas.api.utils.EmbeddedSensitiveData;
import de.symeda.sormas.api.utils.PersonalData;
import de.symeda.sormas.api.utils.SensitiveData;
import de.symeda.sormas.api.utils.fieldaccess.FieldAccessChecker;

public final class PseudonymizedFieldAccessChecker<T> implements FieldAccessChecker<T> {
public abstract class PseudonymizedFieldAccessChecker<T> implements FieldAccessChecker<T> {

private final WrappedFieldAccessChecker wrapped;

public PseudonymizedFieldAccessChecker(
private PseudonymizedFieldAccessChecker(
Class<? extends Annotation> annotation,
Class<? extends Annotation> embeddedAnnotation,
boolean isPseudonymized) {
this.wrapped = new WrappedFieldAccessChecker(annotation, embeddedAnnotation, isPseudonymized);
boolean isPseudonymized,
String serverCountry) {
this.wrapped = new WrappedFieldAccessChecker(annotation, embeddedAnnotation, isPseudonymized, serverCountry);
}

@Override
Expand All @@ -56,24 +58,52 @@ public boolean hasRight(T object) {

private final class WrappedFieldAccessChecker extends AnnotationBasedFieldAccessChecker<T> {

private final String serverCountry;

private WrappedFieldAccessChecker(
Class<? extends Annotation> annotation,
Class<? extends Annotation> embeddedAnnotation,
boolean isPseudonymized) {
boolean isPseudonymized,
String serverCountry) {
super(annotation, embeddedAnnotation, !isPseudonymized, t -> false);
this.serverCountry = serverCountry;
}

@Override
protected boolean isAnnotatedFieldMandatory(Field annotatedField) {
return false;
}

@Override
public boolean isConfiguredForCheck(Field field, boolean withMandatory) {
if (isExcludedForCountry(field, serverCountry)) {
return false;
}
return super.isConfiguredForCheck(field, withMandatory);
}
}

public static <T> PseudonymizedFieldAccessChecker<T> forPersonalData(boolean isPseudonymized) {
return new PseudonymizedFieldAccessChecker<>(PersonalData.class, EmbeddedPersonalData.class, isPseudonymized);
protected abstract boolean isExcludedForCountry(Field field, String serverCountry);

public static <T> PseudonymizedFieldAccessChecker<T> forPersonalData(boolean isPseudonymized, String serverCountry) {
return new PseudonymizedFieldAccessChecker<>(PersonalData.class, EmbeddedPersonalData.class, isPseudonymized, serverCountry) {

@Override
protected boolean isExcludedForCountry(Field field, String serverCountry) {
return field.getAnnotation(PersonalData.class) != null
&& Arrays.asList(field.getAnnotation(PersonalData.class).excludeForCountries()).contains(serverCountry);
}
};
}

public static <T> PseudonymizedFieldAccessChecker<T> forSensitiveData(boolean isPseudonymized) {
return new PseudonymizedFieldAccessChecker<>(SensitiveData.class, EmbeddedSensitiveData.class, isPseudonymized);
public static <T> PseudonymizedFieldAccessChecker<T> forSensitiveData(boolean isPseudonymized, String serverCountry) {
return new PseudonymizedFieldAccessChecker<>(SensitiveData.class, EmbeddedSensitiveData.class, isPseudonymized, serverCountry) {

@Override
protected boolean isExcludedForCountry(Field field, String serverCountry) {
return field.getAnnotation(SensitiveData.class) != null
&& Arrays.asList(field.getAnnotation(SensitiveData.class).excludeForCountries()).contains(serverCountry);
}
};
}
}
Loading
Loading