From 23ff1820ea9c77a69f9d808e4f5244925d92f9e7 Mon Sep 17 00:00:00 2001 From: Harold Date: Thu, 16 Apr 2026 15:03:35 +0300 Subject: [PATCH 1/2] Fixes issue where HealthConditionsForm fields are not accessible to users with the correct rights & roles --- .../CaseFacadeEjbPseudonymizationTest.java | 49 ++++++++++++++++++- .../symeda/sormas/ui/caze/CaseCreateForm.java | 2 + .../symeda/sormas/ui/caze/CaseDataForm.java | 7 ++- .../symeda/sormas/ui/caze/CaseDataView.java | 4 +- .../symeda/sormas/ui/caze/CaseFilterForm.java | 14 ++++-- .../clinicalcourse/HealthConditionsForm.java | 13 ----- .../sormas/ui/contact/ContactDataForm.java | 7 ++- 7 files changed, 71 insertions(+), 25 deletions(-) diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseFacadeEjbPseudonymizationTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseFacadeEjbPseudonymizationTest.java index fce9f57fb6e..ec7d86c60f0 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseFacadeEjbPseudonymizationTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseFacadeEjbPseudonymizationTest.java @@ -436,7 +436,7 @@ public void testUpdateGpsCoordinatesWithPseudonymizedData() { /** * Expected to save the updated data because, it is a really rare edge case that is not handled at the moment. * Probably won't be a need to handle it. - * + * * @see de.symeda.sormas.api.utils.pseudonymization.valuepseudonymizers.LongitudePseudonymizer#isValuePseudonymized(Double) * and * @see de.symeda.sormas.api.utils.pseudonymization.valuepseudonymizers.LatitudePseudonymizer#isValuePseudonymized(Double) @@ -445,6 +445,53 @@ public void testUpdateGpsCoordinatesWithPseudonymizedData() { assertThat(savedCase.getReportLon(), is(22.234)); } + @Test + public void testOtherConditionsHiddenWithoutSensitiveDataRight() { + // A user without SEE_SENSITIVE_DATA_IN_JURISDICTION must not see otherConditions, + // regardless of jurisdiction — it is @SensitiveData and pseudonymized on the backend. + loginWith(nationalAdmin); + UserRoleReferenceDto noSensitiveDataRole = creator.createUserRoleWithRequiredRights( + "ContactPersonNoSensitive", + JurisdictionLevel.DISTRICT, + UserRight.CASE_VIEW, + UserRight.PERSON_VIEW, + UserRight.CLINICAL_COURSE_VIEW, + UserRight.SEE_PERSONAL_DATA_IN_JURISDICTION); + + UserDto contactPerson = + creator.createUser(rdcf1.region.getUuid(), rdcf1.district.getUuid(), rdcf1.facility.getUuid(), "Contact", "Person", noSensitiveDataRole); + + // Create case with otherConditions set at creation time + CaseDataDto caze = creator.createCase( + user1.toReference(), + createPerson().toReference(), + Disease.CORONAVIRUS, + CaseClassification.NOT_CLASSIFIED, + InvestigationStatus.PENDING, + new Date(), + rdcf1, + c -> { + c.setRegion(rdcf1.region); + c.setDistrict(rdcf1.district); + c.setCommunity(rdcf1.community); + c.setReportingUser(user1.toReference()); + c.getHealthConditions().setOtherConditions("Sensitive pre-existing condition"); + }); + + // user without sensitive data right: healthConditions is @SensitiveData so the entire field is pseudonymized (null), + // and isPseudonymized=true because sensitive data was hidden + loginWith(contactPerson); + CaseDataDto result = getCaseFacade().getCaseDataByUuid(caze.getUuid()); + assertThat(result.isPseudonymized(), is(true)); + assertThat(result.getHealthConditions(), is(nullValue())); + + // nationalAdmin has all rights including SEE_SENSITIVE_DATA: otherConditions must be visible + loginWith(nationalAdmin); + CaseDataDto resultWithRight = getCaseFacade().getCaseDataByUuid(caze.getUuid()); + assertThat(resultWithRight.isPseudonymized(), is(false)); + assertThat(resultWithRight.getHealthConditions().getOtherConditions(), is("Sensitive pre-existing condition")); + } + @Test public void testSpecialCaseAccessOutsideJurisdiction() { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseCreateForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseCreateForm.java index 3b44b491f42..2c564d31d2a 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseCreateForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseCreateForm.java @@ -549,8 +549,10 @@ private void hideAndFillJurisdictionFields() { getContent().getComponent(PLACE_OF_STAY_HEADING_LOC).setVisible(false); differentPlaceOfStayJurisdiction.setVisible(false); responsibleRegionCombo.setVisible(false); + responsibleRegionCombo.setRequired(false); responsibleRegionCombo.setValue(FacadeProvider.getRegionFacade().getDefaultInfrastructureReference()); responsibleDistrictCombo.setVisible(false); + responsibleDistrictCombo.setRequired(false); responsibleDistrictCombo.setValue(FacadeProvider.getDistrictFacade().getDefaultInfrastructureReference()); responsibleCommunityCombo.setVisible(false); responsibleCommunityCombo.setValue(FacadeProvider.getCommunityFacade().getDefaultInfrastructureReference()); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java index 16c549d30a5..1e84d8244e2 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataForm.java @@ -127,7 +127,6 @@ import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.ExtendedReduced; import de.symeda.sormas.api.utils.YesNoUnknown; -import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker; import de.symeda.sormas.api.utils.fieldvisibility.checkers.FeatureTypeFieldVisibilityChecker; @@ -303,6 +302,8 @@ public class CaseDataForm extends AbstractEditForm { private final Disease disease; private final SymptomsDto symptoms; private final boolean caseFollowUpEnabled; + private final boolean isPseudonymized; + private final boolean inJurisdiction; private DateField dfFollowUpUntil; private CheckBox cbOverwriteFollowUpUntil; private Field quarantine; @@ -359,6 +360,8 @@ public CaseDataForm( this.disease = disease; this.symptoms = symptoms; this.caseFollowUpEnabled = UiUtil.enabled(FeatureType.CASE_FOLLOWUP); + this.isPseudonymized = isPseudonymized; + this.inJurisdiction = inJurisdiction; addFields(); } @@ -1112,7 +1115,7 @@ protected void addFields() { disease, FieldVisibilityCheckers.withDisease(disease) .add(new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale())), - UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()), + FieldAccessHelper.getFieldAccessCheckers(inJurisdiction, isPseudonymized), new PersonReferenceDto(person.getUuid()))) .setCaption(null); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataView.java index d330539ef5a..601b5f4ce5b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseDataView.java @@ -259,13 +259,13 @@ protected void initView(String params) { layout.addSidePanelComponent(new SideComponentLayout(specialAccessListComponent), SPECIAL_ACCESSES_LOC); } - if (UiUtil.permitted(FeatureType.SELF_REPORTING)) { + if (UiUtil.permitted(FeatureType.SELF_REPORTING, UserRight.SELF_REPORT_VIEW)) { SelfReportListComponent selfReportListComponent = new SelfReportListComponent(SelfReportType.CASE, new SelfReportCriteria().setCaze(new CaseReferenceDto(caze.getUuid()))); SelfReportListComponentLayout selfReportListComponentLayout = new SelfReportListComponentLayout(selfReportListComponent); layout.addSidePanelComponent(selfReportListComponentLayout, SELF_REPORT_LOC); } - if (UiUtil.permitted(FeatureType.SURVEYS)) { + if (UiUtil.permitted(FeatureType.SURVEYS, UserRight.SURVEY_VIEW)) { SurveyListComponentLayout surveyList = new SurveyListComponentLayout( caze.toReference(), caze.getDisease(), diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseFilterForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseFilterForm.java index e79c590f53e..f45cb976e3d 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseFilterForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CaseFilterForm.java @@ -455,7 +455,7 @@ public void addMoreFilters(CustomLayout moreFiltersContainer) { moreFiltersContainer.addComponent(buildWeekAndDateFilter(isExternalShareEnabled), WEEK_AND_DATE_FILTER); moreFiltersContainer.addComponent(buildBirthdayRangeFilter(), BIRTHDATE_RANGE_FILTER); - if (UiUtil.enabled(FeatureType.SURVEYS)) { + if (UiUtil.enabled(FeatureType.SURVEYS) && UiUtil.permitted(UserRight.SURVEY_VIEW)) { buildSurveyFilters(moreFiltersContainer); } } @@ -862,15 +862,19 @@ private HorizontalLayout buildBirthdayRangeFilter() { private void buildSurveyFilters(CustomLayout layout) { - ComboBox surveyCombo = addField(layout, FieldConfiguration.withCaptionAndPixelSized(CaseCriteria.SURVEY, I18nProperties.getString(Strings.promptSurvey), 200)); + ComboBox surveyCombo = + addField(layout, FieldConfiguration.withCaptionAndPixelSized(CaseCriteria.SURVEY, I18nProperties.getString(Strings.promptSurvey), 200)); FieldHelper.updateItems(surveyCombo, FacadeProvider.getSurveyFacade().getAllAsReference()); - addField(layout, + addField( + layout, FieldConfiguration .withCaptionAndPixelSized(CaseCriteria.SURVEY_RESPONSE_STATUS, I18nProperties.getString(Strings.promptSurveyResponseStatus), 200)); - addField(layout, + addField( + layout, FieldConfiguration .withCaptionAndPixelSized(CaseCriteria.SURVEY_ASSIGNED_FROM, I18nProperties.getString(Strings.promptSurveyAssignedFrom), 200)); - addField(layout, + addField( + layout, FieldConfiguration .withCaptionAndPixelSized(CaseCriteria.SURVEY_ASSIGNED_TO, I18nProperties.getString(Strings.promptSurveyAssignedTo), 200)); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java index 23d97141914..2a576970810 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/clinicalcourse/HealthConditionsForm.java @@ -51,7 +51,6 @@ import com.vaadin.ui.Label; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractSelect; -import com.vaadin.v7.ui.AbstractTextField; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextArea; @@ -66,13 +65,11 @@ import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.person.PersonReferenceDto; -import de.symeda.sormas.api.user.UserRight; import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.ControllerProvider; -import de.symeda.sormas.ui.UiUtil; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.FieldHelper; @@ -328,16 +325,6 @@ protected void addFields() { FieldHelper.setVisibleWhen(getFieldGroup(), Arrays.asList(MALARIA_INFECTED_YEAR), MALARIA, Arrays.asList(YesNoUnknown.YES), true); } - if (UiUtil.permitted(UserRight.SEE_SENSITIVE_DATA_IN_JURISDICTION, UserRight.SEE_SENSITIVE_DATA_OUTSIDE_JURISDICTION)) { - Field other = getField(OTHER_CONDITIONS); - if (other != null) { - other.setReadOnly(false); - other.setEnabled(true); - if (other instanceof AbstractTextField) { - ((AbstractTextField) other).setInputPrompt(""); - } - } - } } /** diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataForm.java index 9cd119a39f3..9cd7b27a857 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataForm.java @@ -87,7 +87,6 @@ import de.symeda.sormas.api.utils.Diseases.DiseasesConfiguration; import de.symeda.sormas.api.utils.ExtendedReduced; import de.symeda.sormas.api.utils.YesNoUnknown; -import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker; import de.symeda.sormas.ui.ControllerProvider; @@ -182,6 +181,8 @@ public class ContactDataForm extends AbstractEditForm { private final ViewMode viewMode; private final Disease disease; private final boolean diseaseHasFollowUp; + private final boolean isPseudonymized; + private final boolean inJurisdiction; private OptionGroup contactProximities; private ComboBox region; private ComboBox district; @@ -222,6 +223,8 @@ public ContactDataForm(Disease disease, ViewMode viewMode, boolean isPseudonymiz this.viewMode = viewMode; this.disease = disease; this.diseaseHasFollowUp = FacadeProvider.getDiseaseConfigurationFacade().hasFollowUp(disease); + this.isPseudonymized = isPseudonymized; + this.inJurisdiction = inJurisdiction; addFields(); } @@ -545,7 +548,7 @@ protected void addFields() { disease, FieldVisibilityCheckers.withDisease(disease) .add(new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale())), - UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()))); + FieldAccessHelper.getFieldAccessCheckers(inJurisdiction, isPseudonymized))); clinicalCourseForm.setCaption(null); Label generalCommentLabel = new Label(I18nProperties.getPrefixCaption(ContactDto.I18N_PREFIX, ContactDto.ADDITIONAL_DETAILS)); From 04a636db67bf37be99877327a0067c8a2d42d867 Mon Sep 17 00:00:00 2001 From: Harold Date: Thu, 16 Apr 2026 15:07:27 +0300 Subject: [PATCH 2/2] Fixes issue where HealthConditionsForm fields are not accessible to users with the correct rights & roles --- .../sormas/ui/contact/ContactDataView.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataView.java index b8d28e718d9..2ae252fcd74 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactDataView.java @@ -14,6 +14,8 @@ */ package de.symeda.sormas.ui.contact; +import java.util.List; + import com.vaadin.server.Page; import com.vaadin.shared.ui.ContentMode; import com.vaadin.ui.Button; @@ -74,8 +76,6 @@ import de.symeda.sormas.ui.utils.components.sidecomponent.SideComponentLayout; import de.symeda.sormas.ui.vaccination.list.VaccinationListComponent; -import java.util.List; - public class ContactDataView extends AbstractContactView implements HasName { private static final long serialVersionUID = -1L; @@ -220,13 +220,16 @@ protected void initView(String params) { layout.addSidePanelComponent(taskList, TASKS_LOC); } - if (!(FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG) && List.of(Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.MEASLES).contains(contactDto.getDisease()))) { + if (!(FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG) + && List.of(Disease.INVASIVE_MENINGOCOCCAL_INFECTION, Disease.MEASLES).contains(contactDto.getDisease()))) { if (UiUtil.permitted(UserRight.SAMPLE_VIEW)) { SampleListComponent sampleList = new SampleListComponent( - new SampleCriteria().contact(getContactRef()).disease(contactDto.getDisease()).sampleAssociationType(SampleAssociationType.CONTACT), - this::showUnsavedChangesPopup, - editAllowed, - SampleAssociationType.CONTACT); + new SampleCriteria().contact(getContactRef()) + .disease(contactDto.getDisease()) + .sampleAssociationType(SampleAssociationType.CONTACT), + this::showUnsavedChangesPopup, + editAllowed, + SampleAssociationType.CONTACT); SampleListComponentLayout sampleListComponentLayout = new SampleListComponentLayout(sampleList, null); layout.addSidePanelComponent(sampleListComponentLayout, SAMPLES_LOC); } @@ -310,10 +313,13 @@ protected void initView(String params) { layout.addSidePanelComponent(new SideComponentLayout(externalEmailSideComponent), EXTERNAL_EMAILS_LOC); } - SelfReportListComponent selfReportListComponent = - new SelfReportListComponent(SelfReportType.CONTACT, new SelfReportCriteria().setContact(new ContactReferenceDto(contactDto.getUuid()))); - SelfReportListComponentLayout selfReportListComponentLayout = new SelfReportListComponentLayout(selfReportListComponent); - layout.addSidePanelComponent(selfReportListComponentLayout, SELF_REPORT_LOC); + if (UiUtil.permitted(FeatureType.SELF_REPORTING, UserRight.SELF_REPORT_VIEW)) { + SelfReportListComponent selfReportListComponent = new SelfReportListComponent( + SelfReportType.CONTACT, + new SelfReportCriteria().setContact(new ContactReferenceDto(contactDto.getUuid()))); + SelfReportListComponentLayout selfReportListComponentLayout = new SelfReportListComponentLayout(selfReportListComponent); + layout.addSidePanelComponent(selfReportListComponentLayout, SELF_REPORT_LOC); + } final boolean deleted = FacadeProvider.getContactFacade().isDeleted(uuid); layout.disableIfNecessary(deleted, contactEditAllowed);