Skip to content

Commit

Permalink
feat: securing data endpoints depending on campaign sensitivity (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
davdarras authored Jan 21, 2025
1 parent 3574cb3 commit d3ab67b
Show file tree
Hide file tree
Showing 41 changed files with 1,366 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ public enum AuthorityRoleEnum {
public String securityRole() {
return ROLE_PREFIX + this.name();
}

public static AuthorityRoleEnum fromAuthority(String authority) {
if(authority != null && authority.startsWith(ROLE_PREFIX)) {
authority = authority.split(ROLE_PREFIX)[1];
}
return AuthorityRoleEnum.valueOf(authority);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public List<SurveyUnit> getInterviewerSurveyUnits() {

@Override
public void checkHabilitations(String surveyUnitId, PilotageRole... rolesToCheck) {
SurveyUnitSummary surveyUnit = surveyUnitService.getSurveyUnitWithCampaignById(surveyUnitId);
SurveyUnitSummary surveyUnit = surveyUnitService.getSummaryById(surveyUnitId);
Authentication auth = authHelper.getAuthenticationPrincipal();

List<String> userRoles = auth.getAuthorities().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package fr.insee.queen.application.surveyunit.controller;

import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import fr.insee.queen.application.configuration.auth.AuthorityPrivileges;
import fr.insee.queen.application.configuration.auth.AuthorityRoleEnum;
import fr.insee.queen.application.pilotage.controller.PilotageComponent;
import fr.insee.queen.application.surveyunit.controller.exception.LockedResourceException;
import fr.insee.queen.application.web.authentication.AuthenticationHelper;
import fr.insee.queen.application.web.validation.IdValid;
import fr.insee.queen.application.web.validation.json.JsonValid;
import fr.insee.queen.application.web.validation.json.SchemaType;
import fr.insee.queen.domain.campaign.model.CampaignSensitivity;
import fr.insee.queen.domain.pilotage.service.PilotageRole;
import fr.insee.queen.domain.surveyunit.model.StateData;
import fr.insee.queen.domain.surveyunit.model.StateDataType;
import fr.insee.queen.domain.surveyunit.model.SurveyUnitSummary;
import fr.insee.queen.domain.surveyunit.service.DataService;
import fr.insee.queen.domain.surveyunit.service.StateDataService;
import fr.insee.queen.domain.surveyunit.service.SurveyUnitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
Expand All @@ -16,10 +26,13 @@
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

/**
* These endpoints handle the questionnaire form data of a survey unit
*/
Expand All @@ -32,6 +45,9 @@
public class DataController {
private final DataService dataService;
private final PilotageComponent pilotageComponent;
private final StateDataService stateDataService;
private final SurveyUnitService surveyUnitService;
private final AuthenticationHelper authenticationUserHelper;

/**
* Retrieve the questionnaire form data of a survey unit
Expand All @@ -45,7 +61,37 @@ public class DataController {
@ApiResponse(responseCode = "200", content = {@Content(mediaType = "application/json", schema = @Schema(ref = SchemaType.Names.DATA))})
public ObjectNode getDataBySurveyUnit(@IdValid @PathVariable(value = "id") String surveyUnitId) {
pilotageComponent.checkHabilitations(surveyUnitId, PilotageRole.INTERVIEWER);
return dataService.getData(surveyUnitId);
SurveyUnitSummary surveyUnitSummary = surveyUnitService.getSummaryById(surveyUnitId);

// if campaign sensitivity is OFF, return data
if(surveyUnitSummary.campaign().getSensitivity().equals(CampaignSensitivity.NORMAL)) {
return dataService.getData(surveyUnitId);
}

// here, campaign sensitivity is ON !

// admin can see everything
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.ADMIN, AuthorityRoleEnum.WEBCLIENT)){
return dataService.getData(surveyUnitId);
}

// interviewer retrieve the dto with filled or empty data
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.INTERVIEWER, AuthorityRoleEnum.SURVEY_UNIT)){
Optional<StateDataType> validatedState = stateDataService
.findStateData(surveyUnitId)
.map(StateData::state)
.filter(state -> StateDataType.EXTRACTED.equals(state)
|| StateDataType.VALIDATED.equals(state));

if (validatedState.isPresent()) {
return JsonNodeFactory.instance.objectNode();
}
// if no state data or if state not extracted/validated
return dataService.getData(surveyUnitId);
}

// reviewer cannot see data
throw new AccessDeniedException("Not authorized to see survey unit data");
}


Expand All @@ -67,8 +113,40 @@ public void updateData(
ObjectNode dataValue,
@IdValid
@PathVariable(value = "id")
String surveyUnitId) {
String surveyUnitId) throws LockedResourceException {
pilotageComponent.checkHabilitations(surveyUnitId, PilotageRole.INTERVIEWER);
dataService.saveData(surveyUnitId, dataValue);

SurveyUnitSummary surveyUnitSummary = surveyUnitService.getSummaryById(surveyUnitId);

// if campaign sensitivity is OFF, update data
if(surveyUnitSummary.campaign().getSensitivity().equals(CampaignSensitivity.NORMAL)) {
dataService.saveData(surveyUnitId, dataValue);
return;
}

// here, campaign sensitivity is ON !

// admin can do everything
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.ADMIN, AuthorityRoleEnum.WEBCLIENT)){
dataService.saveData(surveyUnitId, dataValue);
return;
}

// interviewer/survey-unit can update data if survey is not ended
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.INTERVIEWER, AuthorityRoleEnum.SURVEY_UNIT)){
Optional<StateDataType> validatedState = stateDataService
.findStateData(surveyUnitId)
.map(StateData::state)
.filter(state -> StateDataType.EXTRACTED.equals(state)
|| StateDataType.VALIDATED.equals(state));

if (validatedState.isEmpty()) {
dataService.saveData(surveyUnitId, dataValue);
return;
}

throw new LockedResourceException(surveyUnitId);
}
throw new AccessDeniedException("Not authorized to update survey unit data");
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package fr.insee.queen.application.surveyunit.controller;

import fr.insee.queen.application.configuration.auth.AuthorityPrivileges;
import fr.insee.queen.application.configuration.auth.AuthorityRoleEnum;
import fr.insee.queen.application.pilotage.controller.PilotageComponent;
import fr.insee.queen.application.surveyunit.controller.exception.LockedResourceException;
import fr.insee.queen.application.surveyunit.dto.input.StateDataInput;
import fr.insee.queen.application.surveyunit.dto.output.StateDataDto;
import fr.insee.queen.application.surveyunit.dto.output.SurveyUnitDto;
import fr.insee.queen.application.surveyunit.dto.output.SurveyUnitOkNokDto;
import fr.insee.queen.application.web.authentication.AuthenticationHelper;
import fr.insee.queen.application.web.validation.IdValid;
import fr.insee.queen.domain.campaign.model.CampaignSensitivity;
import fr.insee.queen.domain.pilotage.service.PilotageRole;
import fr.insee.queen.domain.surveyunit.model.StateData;
import fr.insee.queen.domain.surveyunit.model.StateDataType;
import fr.insee.queen.domain.surveyunit.model.SurveyUnitState;
import fr.insee.queen.domain.surveyunit.model.SurveyUnitSummary;
import fr.insee.queen.domain.surveyunit.service.StateDataService;
import fr.insee.queen.domain.surveyunit.service.SurveyUnitService;
import fr.insee.queen.domain.surveyunit.service.exception.StateDataInvalidDateException;
Expand All @@ -18,11 +25,13 @@
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

/**
* Handle the data state of a survey unit.
Expand All @@ -37,6 +46,7 @@ public class StateDataController {
private final StateDataService stateDataService;
private final SurveyUnitService surveyUnitService;
private final PilotageComponent pilotageComponent;
private final AuthenticationHelper authenticationUserHelper;

/**
* Retrieve the data linked of a survey unit
Expand All @@ -62,9 +72,39 @@ public StateDataDto getStateDataBySurveyUnit(@IdValid @PathVariable(value = "id"
@PutMapping(path = "/survey-unit/{id}/state-data")
@PreAuthorize(AuthorityPrivileges.HAS_SURVEY_UNIT_PRIVILEGES)
public void setStateData(@IdValid @PathVariable(value = "id") String surveyUnitId,
@Valid @RequestBody StateDataInput stateDataInputDto) throws StateDataInvalidDateException {
@Valid @RequestBody StateDataInput stateDataInputDto) throws StateDataInvalidDateException, LockedResourceException {
pilotageComponent.checkHabilitations(surveyUnitId, PilotageRole.INTERVIEWER);
stateDataService.saveStateData(surveyUnitId, StateDataInput.toModel(stateDataInputDto));
SurveyUnitSummary surveyUnitSummary = surveyUnitService.getSummaryById(surveyUnitId);

// if campaign sensitivity is OFF, update data
if(surveyUnitSummary.campaign().getSensitivity().equals(CampaignSensitivity.NORMAL)) {
stateDataService.saveStateData(surveyUnitId, StateDataInput.toModel(stateDataInputDto));
return;
}

// here, campaign sensitivity is ON !

// admin can do everything
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.ADMIN, AuthorityRoleEnum.WEBCLIENT)){
stateDataService.saveStateData(surveyUnitId, StateDataInput.toModel(stateDataInputDto));
return;
}

// interviewer/survey-unit can update data if survey is not ended
if(authenticationUserHelper.hasRole(AuthorityRoleEnum.INTERVIEWER, AuthorityRoleEnum.SURVEY_UNIT)){
Optional<StateDataType> validatedState = stateDataService
.findStateData(surveyUnitId)
.map(StateData::state)
.filter(state -> StateDataType.EXTRACTED.equals(state)
|| StateDataType.VALIDATED.equals(state));

if (validatedState.isEmpty()) {
stateDataService.saveStateData(surveyUnitId, StateDataInput.toModel(stateDataInputDto));
return;
}
throw new LockedResourceException(surveyUnitId);
}
throw new AccessDeniedException("Not authorized to update survey unit data");
}

/**
Expand Down
Loading

0 comments on commit d3ab67b

Please sign in to comment.