Skip to content

Commit

Permalink
Merge pull request #92 from MeasureAuthoringTool/feature/MAT-7918/sup…
Browse files Browse the repository at this point in the history
…port-removing-share-permissions-from-library

MAT-7918: support granting and revoking ACLs on library
  • Loading branch information
etan-sb authored Nov 27, 2024
2 parents 0d87691 + 9986778 commit ea7f962
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 124 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.6.70-SNAPSHOT</version>
<version>0.6.71-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>gov.cms.madie</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import gov.cms.madie.cqllibraryservice.services.LibrarySetService;
import gov.cms.madie.cqllibraryservice.utils.AuthUtils;
import gov.cms.madie.cqllibraryservice.utils.LibraryUtils;
import gov.cms.madie.models.access.AclOperation;
import gov.cms.madie.models.access.AclSpecification;
import gov.cms.madie.models.common.ActionType;
import gov.cms.madie.models.dto.LibraryUsage;
import gov.cms.madie.models.library.CqlLibrary;
Expand Down Expand Up @@ -218,27 +220,16 @@ public ResponseEntity<String> changeOwnership(
return response;
}

@PutMapping(
value = "/{id}/grant",
produces = {MediaType.TEXT_PLAIN_VALUE})
@PutMapping("/{id}/acls")
@PreAuthorize("#request.getHeader('api-key') == #apiKey")
public ResponseEntity<String> grantAccess(
public ResponseEntity<List<AclSpecification>> updateAccessControl(
HttpServletRequest request,
@PathVariable("id") String id,
@RequestParam(required = true, name = "userid") String userid,
@PathVariable String id,
@RequestBody @Validated AclOperation aclOperation,
@Value("${admin-api-key}") String apiKey) {
ResponseEntity<String> response =
ResponseEntity.badRequest().body("Cql Library does not exist.");

log.info("getLibraryId [{}] using apiKey ", id);

if (cqlLibraryService.grantAccess(id, userid)) {
response =
ResponseEntity.ok()
.body(String.format("%s granted access to Library successfully.", userid));
actionLogService.logAction(id, ActionType.UPDATED, "apiKey");
}
return response;
List<AclSpecification> aclSpecifications =
cqlLibraryService.updateAccessControlList(id, aclOperation);
return ResponseEntity.ok().body(aclSpecifications);
}

@GetMapping("/sharedWith")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import gov.cms.madie.cqllibraryservice.dto.LibraryListDTO;
import gov.cms.madie.cqllibraryservice.exceptions.*;
import gov.cms.madie.cqllibraryservice.repositories.LibrarySetRepository;
import gov.cms.madie.models.access.RoleEnum;
import gov.cms.madie.models.access.AclOperation;
import gov.cms.madie.models.access.AclSpecification;
import gov.cms.madie.models.common.ActionType;
import gov.cms.madie.models.common.Version;
import gov.cms.madie.models.dto.LibraryUsage;
import gov.cms.madie.models.library.CqlLibrary;
Expand All @@ -29,6 +31,7 @@ public class CqlLibraryService {
private final ElmTranslatorClient elmTranslatorClient;
private final LibrarySetRepository librarySetRepository;
private CqlLibraryRepository cqlLibraryRepository;
private final ActionLogService actionLogService;
private LibrarySetService librarySetService;
private MeasureServiceClient measureServiceClient;

Expand Down Expand Up @@ -98,24 +101,26 @@ public CqlLibrary findCqlLibraryById(String id) {
throw new ResourceNotFoundException("CQL Library", id);
}

public boolean changeOwnership(String id, String userid) {
boolean result = false;
Optional<CqlLibrary> persistedCqlLibrary = cqlLibraryRepository.findById(id);
if (persistedCqlLibrary.isPresent()) {
CqlLibrary cqlLibrary = persistedCqlLibrary.get();
librarySetService.updateOwnership(cqlLibrary.getLibrarySetId(), userid);
result = true;
public List<AclSpecification> updateAccessControlList(
String cqlLibraryId, AclOperation aclOperation) {
Optional<CqlLibrary> persistedLibrary = cqlLibraryRepository.findById(cqlLibraryId);
if (persistedLibrary.isEmpty()) {
throw new ResourceNotFoundException("Library does not exist: " + cqlLibraryId);
}
return result;

CqlLibrary library = persistedLibrary.get();
LibrarySet librarySet =
librarySetService.updateLibrarySetAcls(library.getLibrarySetId(), aclOperation);
actionLogService.logAction(cqlLibraryId, ActionType.UPDATED, "admin");
return librarySet.getAcls();
}

public boolean grantAccess(String cqlLibraryId, String userid) {
public boolean changeOwnership(String id, String userid) {
boolean result = false;
Optional<CqlLibrary> persistedCqlLibrary = cqlLibraryRepository.findById(cqlLibraryId);
Optional<CqlLibrary> persistedCqlLibrary = cqlLibraryRepository.findById(id);
if (persistedCqlLibrary.isPresent()) {
CqlLibrary cqlLibrary = persistedCqlLibrary.get();
librarySetService.updateLibrarySetAcls(
cqlLibrary.getLibrarySetId(), userid, RoleEnum.SHARED_WITH);
librarySetService.updateOwnership(cqlLibrary.getLibrarySetId(), userid);
result = true;
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import gov.cms.madie.cqllibraryservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.cqllibraryservice.repositories.LibrarySetRepository;
import gov.cms.madie.models.access.AclOperation;
import gov.cms.madie.models.access.AclSpecification;
import gov.cms.madie.models.access.RoleEnum;
import gov.cms.madie.models.common.ActionType;
import gov.cms.madie.models.library.LibrarySet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Slf4j
Expand All @@ -36,52 +36,75 @@ public void createLibrarySet(
}
}

public LibrarySet updateLibrarySetAcls(String librarySetId, String userId, RoleEnum role) {
public LibrarySet updateLibrarySetAcls(String librarySetId, AclOperation aclOperation) {
Optional<LibrarySet> optionalLibrarySet = librarySetRepository.findByLibrarySetId(librarySetId);
if (optionalLibrarySet.isPresent()) {
LibrarySet librarySet = optionalLibrarySet.get();
if (CollectionUtils.isEmpty(librarySet.getAcls())) {
librarySet.setAcls(List.of(createAcl(userId, role)));
} else {
Optional<AclSpecification> aclSpecification = getAclSpecification(userId, librarySet);
if (aclSpecification.isPresent()) {
AclSpecification specification = aclSpecification.get();
if (!specification.getRoles().contains(role)) {
specification.getRoles().add(role);
}
if (AclOperation.AclAction.GRANT == aclOperation.getAction()) {
if (CollectionUtils.isEmpty(librarySet.getAcls())) {
// if no acl present, add it
librarySet.setAcls(aclOperation.getAcls());
} else {
librarySet.getAcls().add(createAcl(userId, role));
// update acl
aclOperation
.getAcls()
.forEach(
acl -> {
// check if acl already present for the user
AclSpecification aclSpecification =
findAclSpecificationByUserId(librarySet, acl.getUserId());
// if acl does not present, add it
if (aclSpecification == null) {
librarySet.getAcls().add(acl);
} else {
aclSpecification.getRoles().addAll(acl.getRoles());
}
});
}
} else if (AclOperation.AclAction.REVOKE == aclOperation.getAction()) {
aclOperation
.getAcls()
.forEach(
acl -> {
// check if acl already present for the user
AclSpecification aclSpecification =
findAclSpecificationByUserId(librarySet, acl.getUserId());
if (aclSpecification != null) {
// remove roles from ACL
aclSpecification.getRoles().removeAll(acl.getRoles());
// after removing the roles if there is no role left, remove acl
if (aclSpecification.getRoles().isEmpty()) {
librarySet.getAcls().remove(aclSpecification);
}
}
});
}

LibrarySet updatedLibrarySet = librarySetRepository.save(librarySet);
log.info("SHARED acl added to library set [{}]", updatedLibrarySet.getId());
log.info("ACL updated for Library set [{}]", updatedLibrarySet.getId());
return updatedLibrarySet;
} else {
String error =
String.format(
"Library with set id `%s` can not be shared. Library set may not exists.",
librarySetId, userId);
librarySetId);
log.error(error);
throw new ResourceNotFoundException("LibrarySet", "id", librarySetId);
}
}

private AclSpecification createAcl(String userId, RoleEnum role) {
AclSpecification spec = new AclSpecification();
spec.setUserId(userId);
spec.setRoles(List.of(role));

return spec;
public LibrarySet findByLibrarySetId(final String librarySetId) {
return librarySetRepository.findByLibrarySetId(librarySetId).orElse(null);
}

public Optional<AclSpecification> getAclSpecification(String userId, LibrarySet librarySet) {
private AclSpecification findAclSpecificationByUserId(LibrarySet librarySet, String userId) {
if (CollectionUtils.isEmpty(librarySet.getAcls())) {
return null;
}
return librarySet.getAcls().stream()
.filter(acl -> userId.equalsIgnoreCase(acl.getUserId()))
.findFirst();
}

public LibrarySet findByLibrarySetId(final String librarySetId) {
return librarySetRepository.findByLibrarySetId(librarySetId).orElse(null);
.filter(existingAcl -> Objects.equals(existingAcl.getUserId(), userId))
.findFirst()
.orElse(null);
}

public LibrarySet updateOwnership(String librarySetId, String userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Set;

@Slf4j
public class AuthUtils {

public static void checkAccessPermissions(CqlLibrary cqlLibrary, String username) {
// TODO: hardcoded allowed ACLs for now
List<RoleEnum> allowedRoles = List.of(RoleEnum.SHARED_WITH);
Set<RoleEnum> allowedRoles = Set.of(RoleEnum.SHARED_WITH);
if (!username.equalsIgnoreCase(cqlLibrary.getLibrarySet().getOwner())
&& (CollectionUtils.isEmpty(cqlLibrary.getLibrarySet().getAcls())
|| cqlLibrary.getLibrarySet().getAcls().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import gov.cms.madie.models.library.LibrarySet;

Expand Down Expand Up @@ -1128,7 +1129,7 @@ public void testAdminMultipleMeasuresGetSharedWith() throws Exception {
CqlLibrary lib2 = CqlLibrary.builder().id("6789").build();
AclSpecification acl1 = new AclSpecification();
acl1.setUserId("raoulduke");
acl1.setRoles(List.of(RoleEnum.SHARED_WITH));
acl1.setRoles(Set.of(RoleEnum.SHARED_WITH));

List<AclSpecification> acls = List.of(acl1);
LibrarySet librarySet = LibrarySet.builder().acls(acls).build();
Expand All @@ -1155,7 +1156,7 @@ public void testAdminMeasureGetSharedWith() throws Exception {
CqlLibrary testLibrary = CqlLibrary.builder().id("12345").build();
AclSpecification acl1 = new AclSpecification();
acl1.setUserId("raoulduke");
acl1.setRoles(List.of(RoleEnum.SHARED_WITH));
acl1.setRoles(Set.of(RoleEnum.SHARED_WITH));

List<AclSpecification> acls = List.of(acl1);
LibrarySet librarySet = LibrarySet.builder().acls(acls).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import gov.cms.madie.cqllibraryservice.exceptions.ResourceNotFoundException;
import gov.cms.madie.cqllibraryservice.services.ActionLogService;
import gov.cms.madie.cqllibraryservice.services.LibrarySetService;
import gov.cms.madie.models.access.AclOperation;
import gov.cms.madie.models.access.AclSpecification;
import gov.cms.madie.models.access.RoleEnum;
import gov.cms.madie.models.common.ActionType;
import gov.cms.madie.models.dto.LibraryUsage;
import gov.cms.madie.models.library.CqlLibrary;
Expand All @@ -34,6 +37,7 @@
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import gov.cms.madie.models.library.LibrarySet;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -528,4 +532,30 @@ void testDeleteLibraryAlongWithVersions() {
response.getBody(),
is(equalTo("The library and all its associated versions have been removed successfully.")));
}

@Test
public void testUpdateAccessControl() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("api-key", "key");

AclSpecification aclSpecification = new AclSpecification();
aclSpecification.setUserId("user_1");
aclSpecification.setRoles(Set.of(RoleEnum.SHARED_WITH));

AclOperation aclOperation =
AclOperation.builder()
.acls(List.of(aclSpecification))
.action(AclOperation.AclAction.GRANT)
.build();

List<AclSpecification> aclSpecifications = List.of(aclSpecification);

when(cqlLibraryService.updateAccessControlList(anyString(), any()))
.thenReturn(aclSpecifications);

ResponseEntity<List<AclSpecification>> output = cqlLibraryController.updateAccessControl(request, "1", aclOperation, "key");

verify(cqlLibraryService, times(1)).updateAccessControlList(anyString(), any());
assertThat(output.getBody(), equalTo(aclSpecifications));
}
}
Loading

0 comments on commit ea7f962

Please sign in to comment.