Skip to content

Commit

Permalink
#1336 | Enforce RegisterSubject or EditSubject privilege check on sub…
Browse files Browse the repository at this point in the history
…ject save from DEA

- Added group_privilege.is_voided check for native entity based access control check queries
  • Loading branch information
1t5j0y committed Sep 19, 2024
1 parent d8dcdb8 commit b9a82de
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,18 @@ default User getUser(String userId) {

Optional<User> findByPhoneNumber(String phoneNumber);

@Query(value = "select (count(p.id) > 0) as exists from group_privilege\n" +
String BASE_PRIVILEGE_QUERY = "select (count(p.id) > 0) as exists from group_privilege\n" +
" join privilege p on group_privilege.privilege_id = p.id\n" +
" join groups on group_privilege.group_id = groups.id and groups.is_voided = false\n" +
" join user_group ug on groups.id = ug.group_id and ug.is_voided = false\n" +
" join users on ug.user_id = users.id\n" +
"where p.type = :type and users.id = :userId and group_privilege.allow and group_privilege.is_voided = false", nativeQuery = true)
"where users.id = :userId and group_privilege.allow and group_privilege.is_voided = false";
String PRIVILEGE_TYPE_CLAUSE = " and p.type = :type";
String PRIVILEGE_TYPES_CLAUSE = " and p.type in :types";
@Query(value = BASE_PRIVILEGE_QUERY + PRIVILEGE_TYPE_CLAUSE, nativeQuery = true)
boolean hasPrivilege(String type, long userId);

@Query(value = "select (count(p.id) > 0) as exists from group_privilege\n" +
" join privilege p on group_privilege.privilege_id = p.id\n" +
" join groups on group_privilege.group_id = groups.id and groups.is_voided = false\n" +
" join user_group ug on groups.id = ug.group_id and ug.is_voided = false\n" +
" join users on ug.user_id = users.id\n" +
"where p.type in :types and users.id = :userId and group_privilege.allow and group_privilege.is_voided = false", nativeQuery = true)
@Query(value = BASE_PRIVILEGE_QUERY + PRIVILEGE_TYPES_CLAUSE, nativeQuery = true)
boolean hasAnyOfSpecificPrivileges(List<String> types, long userId);

@Query(value = "select bool_or(groups.has_all_privileges) from users\n" +
Expand All @@ -105,16 +103,22 @@ default User getUser(String userId) {
"where users.id = :userId", nativeQuery = true)
Boolean hasAll(long userId);

String BASE_ENTITY_TYPE_PRIVILEGE_QUERY = "select (count(p.id) > 0) as exists from group_privilege\n" +
String BASE_ENTITY_TYPE_QUERY = "select (count(p.id) > 0) as exists from group_privilege\n" +
" join privilege p on group_privilege.privilege_id = p.id\n" +
" join groups on group_privilege.group_id = groups.id and groups.is_voided=false\n" +
" join user_group ug on groups.id = ug.group_id and ug.is_voided=false\n" +
" join users on ug.user_id = users.id\n" +
"where p.type = :type and users.id = :userId and group_privilege.allow";
"where users.id = :userId and group_privilege.allow and group_privilege.is_voided = false";

String BASE_ENTITY_TYPE_PRIVILEGE_QUERY = BASE_ENTITY_TYPE_QUERY + PRIVILEGE_TYPE_CLAUSE;
String BASE_ENTITY_TYPE_PRIVILEGE_LIST_QUERY = BASE_ENTITY_TYPE_QUERY + PRIVILEGE_TYPES_CLAUSE;

@Query(value = BASE_ENTITY_TYPE_PRIVILEGE_QUERY + " and group_privilege.subject_type_id = :subjectTypeId", nativeQuery = true)
boolean hasSubjectPrivilege(String type, long subjectTypeId, long userId);

@Query(value = BASE_ENTITY_TYPE_PRIVILEGE_LIST_QUERY + " and group_privilege.subject_type_id = :subjectTypeId", nativeQuery = true)
boolean hasAnyOfSpecificSubjectPrivileges(List<String> types, long subjectTypeId, long userId);

@Query(value = BASE_ENTITY_TYPE_PRIVILEGE_QUERY + " and group_privilege.program_id = :programId", nativeQuery = true)
boolean hasProgramPrivilege(String type, long programId, long userId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public void checkSubjectPrivilege(PrivilegeType privilegeType, @NotNull String s
this.checkSubjectPrivilege(UserContextHolder.getUser(), privilegeType, subjectTypeUUID);
}

public void checkHasAnyOfSpecificSubjectPrivileges(List<PrivilegeType> privilegeTypes, @NotNull String subjectTypeUUID) {
this.checkHasAnyOfSpecificSubjectPrivileges(UserContextHolder.getUser(), privilegeTypes, subjectTypeUUID);
}

public void checkSubjectPrivilege(PrivilegeType privilegeType, Individual individual) {
this.checkSubjectPrivilege(UserContextHolder.getUser(), privilegeType, individual.getSubjectType().getUuid());
}
Expand Down Expand Up @@ -112,6 +116,15 @@ public void checkSubjectPrivilege(User contextUser, PrivilegeType privilegeType,
}
}

public void checkHasAnyOfSpecificSubjectPrivileges(User contextUser, List<PrivilegeType> privilegeTypes, @NotNull String subjectTypeUUID) {
if (userExistsAndHasAllPrivileges(contextUser)) return;
SubjectType subjectType = subjectTypeRepository.findByUuid(subjectTypeUUID);
if (subjectType == null) return;
if (!userRepository.hasAnyOfSpecificSubjectPrivileges(privilegeTypes.stream().map(Enum::name).collect(Collectors.toList()), subjectType.getId(), contextUser.getId())) {
throw AvniAccessException.createNoPrivilegeException(privilegeTypes);
}
}

public void checkProgramPrivilege(PrivilegeType privilegeType, ProgramEnrolment programEnrolment) {
checkProgramPrivilege(UserContextHolder.getUser(), privilegeType, programEnrolment.getProgram().getUuid());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@
import org.springframework.web.bind.annotation.*;

import javax.transaction.Transactional;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.*;
import java.util.stream.Collectors;

import static org.avni.server.web.resourceProcessors.ResourceProcessor.addAuditFields;
Expand Down Expand Up @@ -352,21 +349,22 @@ public Resource<Individual> process(Resource<Individual> resource) {
@Transactional
@PreAuthorize(value = "hasAnyAuthority('user')")
public AvniEntityResponse saveForWeb(@RequestBody IndividualRequest individualRequest) {
accessControlService.checkHasAnyOfSpecificSubjectPrivileges(Arrays.asList(PrivilegeType.EditSubject, PrivilegeType.RegisterSubject), individualRequest.getSubjectTypeUUID());
try {
logger.info(String.format("Saving individual with UUID %s", individualRequest.getUuid()));
logger.info(String.format("Saving individual with UUID %s", individualRequest.getUuid()));
Individual savedIndividual = individualRepository.findEntity(individualRequest.getUuid());
//Subject is changed after this line, hence the following line cannot be moved down closer to its usage
SubjectPartitionData subjectPartitionData = SubjectPartitionData.create(savedIndividual);

Individual individual = createIndividual(individualRequest);
Individual individual = createIndividual(individualRequest);

FormMapping formMapping = formMappingService.findBy(individual.getSubjectType(), null, null, FormType.IndividualProfile);
entityApprovalStatusService.createStatus(EntityApprovalStatus.EntityType.Subject, individual.getId(), ApprovalStatus.Status.Pending, individual.getSubjectType().getUuid(), formMapping);
FormMapping formMapping = formMappingService.findBy(individual.getSubjectType(), null, null, FormType.IndividualProfile);
entityApprovalStatusService.createStatus(EntityApprovalStatus.EntityType.Subject, individual.getId(), ApprovalStatus.Status.Pending, individual.getSubjectType().getUuid(), formMapping);
// Sync attribute values are picked from the field on individual and not from observations, hence this should be done after the individual is saved
txDataControllerHelper.checkSubjectAccess(individual, subjectPartitionData);
logger.info(String.format("Saved individual with UUID %s", individualRequest.getUuid()));
logger.info(String.format("Saved individual with UUID %s", individualRequest.getUuid()));

return new AvniEntityResponse(individual);
return new AvniEntityResponse(individual);
} catch (TxDataControllerHelper.TxDataPartitionAccessDeniedException e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AvniEntityResponse.error(e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;

import static java.lang.String.format;
Expand Down Expand Up @@ -175,10 +175,10 @@ public ResponseEntity<?> saveImage(@RequestParam MultipartFile file, @RequestPar
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.TEXT_PLAIN).body("Unsupported file. Use .bmp, .jpg, .jpeg, .png, .gif file.");
}
if (folder.equals(MediaFolder.ICONS) && isInvalidDimension(tempSourceFile, imageType)) {
accessControlService.checkHasAnyOfSpecificPrivileges(new ArrayList<PrivilegeType>() {{
add(PrivilegeType.EditSubjectType);
add(PrivilegeType.EditOfflineDashboardAndReportCard);
}});
accessControlService.checkHasAnyOfSpecificPrivileges(Arrays.asList(
PrivilegeType.EditSubjectType,
PrivilegeType.EditOfflineDashboardAndReportCard
));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.TEXT_PLAIN).body("Unsupported file. Use image of size 75 X 75 or smaller.");
}
if (folder.equals(MediaFolder.NEWS) && isInvalidImageSize(file)) {
Expand All @@ -187,10 +187,10 @@ public ResponseEntity<?> saveImage(@RequestParam MultipartFile file, @RequestPar
}
if (folder.equals(MediaFolder.PROFILE_PICS)) {
// not checking privileges for specific subject type here as that will be checked while saving the entity
accessControlService.checkHasAnyOfSpecificPrivileges(new ArrayList<PrivilegeType>() {{
add(PrivilegeType.RegisterSubject);
add(PrivilegeType.EditSubject);
}});
accessControlService.checkHasAnyOfSpecificPrivileges(Arrays.asList(
PrivilegeType.RegisterSubject,
PrivilegeType.EditSubject
));
}
String uuid = UUID.randomUUID().toString();
String targetFilePath = format("%s/%s%s", folderName, uuid, imageType.EXT);
Expand Down

0 comments on commit b9a82de

Please sign in to comment.