diff --git a/.gitignore b/.gitignore index fb437d4ca8..c770a83d86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +Makefile WebAPIConfig/ *application.properties .idea/ @@ -12,6 +13,7 @@ sandbox/ /nbactions*.xml *~ .DS_Store +.factorypath ### Developer's personal properties ### **/resources/config/application*-dev-*.properties diff --git a/Makefile b/Makefile index adb76d0ea7..84b1aaac79 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,21 @@ compile: - mvn clean - mvn compile -Pwebapi-postgresql-laertes + mvn clean compile -DskipUnitTests -DskipITtests -s WebAPIConfig/settings.xml -P webapi-postgresql package: compile - mvn package -Pwebapi-postgresql-laertes + mvn package -DskipUnitTests -DskipITtests -s WebAPIConfig/settings.xml -P webapi-postgresql -deploy: package - sudo service tomcat7 stop - sleep 4 - sudo rm -rf /var/lib/tomcat7/webapps/WebAPI* - sudo cp -r target/WebAPI.war /var/lib/tomcat7/webapps/ - sudo chown tomcat7 /var/lib/tomcat7/webapps/WebAPI.war - sudo chgrp tomcat7 /var/lib/tomcat7/webapps/WebAPI.war - sudo service tomcat7 start +deploy: package + /home/ubuntu/Downloads/apache-tomcat-8.5.84-DEV/bin/shutdown.sh + mv /home/ubuntu/Downloads/apache-tomcat-8.5.84-DEV/webapps/WebAPI /mnt/disk1/webapi-dev-tmp/WebAPI-FOLDER-`date +%m%d%H%S` + mv /home/ubuntu/Downloads/apache-tomcat-8.5.84-DEV/webapps/WebAPI.war /mnt/disk1/webapi-dev-tmp/WebAPI.war-`date +%m%d%H%S` + mv target/WebAPI.war /home/ubuntu/Downloads/apache-tomcat-8.5.84-DEV/webapps/ + echo "Now run /home/ubuntu/Downloads/apache-tomcat-8.5.84-DEV/bin/startup.sh" git-push: - git push myfork master + git push test: - wget -O tests/test-general-evidence.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/752061" - wget -O tests/test-drug-hoi.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/752061-374013" - wget -O tests/test-drug.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drug/752061" - wget -O tests/test-hoi.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/hoi/320073" - wget -O tests/test-info.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/info" - wget -O tests/test-drug-hoi-eu-spc.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/904351-4190045" - wget -O tests/test-drug-hoi-splicer.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/19133853-195588" - wget -O tests/test-drug-hoi-faers-counts-and-signals.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/1154343-433031" - wget -O tests/test-drug-hoi-pubmed-mesh-cr.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/1154343-433031" - wget -O tests/test-drug-hoi-pubmed-mesh-clin-trial.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/789578-378144" - wget -O tests/test-drug-hoi-pubmed-mesh-other.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/19010482-316866" - wget -O tests/test-drug-hoi-semmed-cr.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/1112807-441202" - wget -O tests/test-drug-hoi-semmed-clin-trial.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drughoi/19059744-381591" - wget -O tests/test-drug-rollup-ingredient.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drugrollup/ingredient/1000632" - wget -O tests/test-drug-rollup-clin-drug.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drugrollup/clinicaldrug/19074181" - wget -O tests/test-drug-rollup-branded-drug.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/drugrollup/brandeddrug/1000640" - wget -O tests/test-rdf-evidencesummary.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/evidencesummary?conditionID=139900&drugID=1115008&evidenceGroup=Literature" - wget -O tests/test-rdf-evidencedetails.json "http://localhost:8080/WebAPI/LAERTES_CDM/evidence/evidencedetails?conditionID=24134&drugID=1115008&evidenceType=SPL_SPLICER_ADR" + wget -O /tmp/tests/test-drug-rollup-branded-drug.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drugrollup/brandeddrug/1000640" test-public: - wget -O tests/test-general-evidence.json "http://api.ohdsi.org/WebAPI/CS1/evidence/1000640" - wget -O /tmp/tests/test-drug-hoi.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/1000640-137682" - wget -O /tmp/tests/test-drug.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drug/1000640" - wget -O /tmp/tests/test-hoi.json "http://api.ohdsi.org/WebAPI/CS1/evidence/hoi/320073" - wget -O /tmp/tests/test-info.json "http://api.ohdsi.org/WebAPI/CS1/evidence/info" - wget -O /tmp/tests/test-drug-hoi-eu-spc.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/40239056-75053" - wget -O /tmp/tests/test-drug-hoi-splicer.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/19133853-195588" - wget -O /tmp/tests/test-drug-hoi-faers-counts-and-signals.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/1154343-433031" - wget -O /tmp/tests/test-drug-hoi-pubmed-mesh-cr.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/1154343-433031" - wget -O /tmp/tests/test-drug-hoi-pubmed-mesh-clin-trial.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/789578-378144" - wget -O /tmp/tests/test-drug-hoi-pubmed-mesh-other.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/19010482-316866" - wget -O /tmp/tests/test-drug-hoi-semmed-cr.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/1782521-45612000" - wget -O /tmp/tests/test-drug-hoi-semmed-clin-trial.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drughoi/1303425-45616736" - wget -O /tmp/tests/test-drug-rollup-ingredient.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drugrollup/ingredient/1000632" - wget -O /tmp/tests/test-drug-rollup-clin-drug.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drugrollup/clinicaldrug/19074181" wget -O /tmp/tests/test-drug-rollup-branded-drug.json "http://api.ohdsi.org/WebAPI/CS1/evidence/drugrollup/brandeddrug/1000640" diff --git a/pom.xml b/pom.xml index b4ef035fea..6b1bd807c4 100644 --- a/pom.xml +++ b/pom.xml @@ -192,6 +192,11 @@ true authDataSource + + + + true + 8080 diff --git a/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcController.java b/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcController.java index af2aa3a3f3..13aaff54b6 100644 --- a/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcController.java +++ b/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcController.java @@ -42,9 +42,11 @@ import org.ohdsi.webapi.versioning.dto.VersionUpdateDTO; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestBody; import javax.ws.rs.Consumes; @@ -63,6 +65,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -86,6 +89,9 @@ public class CcController { private CharacterizationChecker checker; private PermissionService permissionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + public CcController( final CcService service, final FeAnalysisService feAnalysisService, @@ -151,11 +157,28 @@ public CohortCharacterizationDTO copy(@PathParam("id") final Long id) { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Page list(@Pagination Pageable pageable) { - return service.getPage(pageable).map(entity -> { - CcShortDTO dto = convertCcToShortDto(entity); - permissionService.fillWriteAccess(entity, dto); - return dto; - }); + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return service.getPage(pageable).map(entity -> { + CcShortDTO dto = convertCcToShortDto(entity); + permissionService.fillWriteAccess(entity, dto); + permissionService.fillReadAccess(entity, dto); + return dto; + }); + } else { // filter out what the user does not have read access to + List dtolist = new ArrayList(); + + Page newpage = service.getPage(pageable); + + for (CohortCharacterizationEntity entity : newpage) { + if(permissionService.hasReadAccess(entity)){ + CcShortDTO dto = convertCcToShortDto(entity); + permissionService.fillWriteAccess(entity, dto); + permissionService.fillReadAccess(entity, dto); + dtolist.add(dto); + } + } + return new PageImpl(dtolist, pageable, dtolist.size()); + } } /** diff --git a/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java b/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java index 90c7b7fa33..9875bc8e5e 100644 --- a/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java +++ b/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.stereotype.Controller; @@ -70,7 +71,10 @@ public class EstimationController { private final ScriptExecutionService executionService; private EstimationChecker checker; private PermissionService permissionService; - + + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + public EstimationController(EstimationService service, GenericConversionService conversionService, CommonGenerationSensitiveInfoService sensitiveInfoService, @@ -97,14 +101,26 @@ public EstimationController(EstimationService service, @Path("/") @Produces(MediaType.APPLICATION_JSON) public List getAnalysisList() { - - return StreamSupport.stream(service.getAnalysisList().spliterator(), false) - .map(analysis -> { - EstimationShortDTO dto = conversionService.convert(analysis, EstimationShortDTO.class); - permissionService.fillWriteAccess(analysis, dto); - return dto; - }) - .collect(Collectors.toList()); + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return StreamSupport.stream(service.getAnalysisList().spliterator(), false) + .map(analysis -> { + EstimationShortDTO dto = conversionService.convert(analysis, EstimationShortDTO.class); + permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); + return dto; + }) + .collect(Collectors.toList()); + } else { + return StreamSupport.stream(service.getAnalysisList().spliterator(), false) + .filter(candidateEstimation -> permissionService.hasReadAccess(candidateEstimation)) + .map(analysis -> { + EstimationShortDTO dto = conversionService.convert(analysis, EstimationShortDTO.class); + permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); + return dto; + }) + .collect(Collectors.toList()); + } } /** diff --git a/src/main/java/org/ohdsi/webapi/pathway/PathwayController.java b/src/main/java/org/ohdsi/webapi/pathway/PathwayController.java index 04c50a1434..671e6119b5 100644 --- a/src/main/java/org/ohdsi/webapi/pathway/PathwayController.java +++ b/src/main/java/org/ohdsi/webapi/pathway/PathwayController.java @@ -25,8 +25,10 @@ import org.ohdsi.webapi.versioning.dto.VersionDTO; import org.ohdsi.webapi.versioning.dto.VersionUpdateDTO; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; @@ -34,6 +36,7 @@ import javax.transaction.Transactional; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -53,6 +56,9 @@ public class PathwayController { private PathwayChecker checker; private PermissionService permissionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + @Autowired public PathwayController(ConversionService conversionService, ConverterUtils converterUtils, PathwayService pathwayService, SourceService sourceService, CommonGenerationSensitiveInfoService sensitiveInfoService, PathwayChecker checker, PermissionService permissionService, I18nService i18nService) { @@ -156,13 +162,29 @@ public PathwayAnalysisDTO importAnalysis(final PathwayAnalysisExportDTO dto) { @Consumes(MediaType.APPLICATION_JSON) @Transactional public Page list(@Pagination Pageable pageable) { - - return pathwayService.getPage(pageable).map(pa -> { - PathwayAnalysisDTO dto = conversionService.convert(pa, PathwayAnalysisDTO.class); - permissionService.fillWriteAccess(pa, dto); - return dto; - }); + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return pathwayService.getPage(pageable).map(pa -> { + PathwayAnalysisDTO dto = conversionService.convert(pa, PathwayAnalysisDTO.class); + permissionService.fillWriteAccess(pa, dto); + permissionService.fillReadAccess(pa, dto); + return dto; + }); + } else { // filter out entities that the user does not have read permissions to view + List dtolist = new ArrayList(); + + Page newpage = pathwayService.getPage(pageable); + for (PathwayAnalysisEntity pa : newpage) { + if (permissionService.hasReadAccess(pa)) { + PathwayAnalysisDTO dto = conversionService.convert(pa, PathwayAnalysisDTO.class); + permissionService.fillWriteAccess(pa, dto); + permissionService.fillReadAccess(pa, dto); + dtolist.add(dto); + } + } + return new PageImpl(dtolist, pageable, dtolist.size()); + } } + /** * Check that a pathway analysis name exists. diff --git a/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java b/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java index 67a35b7b55..0838f2363a 100644 --- a/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java +++ b/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.stereotype.Controller; @@ -67,6 +68,9 @@ public class PredictionController { private PermissionService permissionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + @Autowired public PredictionController(PredictionService service, GenericConversionService conversionService, @@ -93,26 +97,40 @@ public PredictionController(PredictionService service, @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) - public List getAnalysisList() { - - return StreamSupport - .stream(service.getAnalysisList().spliterator(), false) - .map(analysis -> { - CommonAnalysisDTO dto = conversionService.convert(analysis, CommonAnalysisDTO.class); - permissionService.fillWriteAccess(analysis, dto); - return dto; - }) - .collect(Collectors.toList()); - } - - /** - * Check to see if a prediction design exists by name - * - * @summary Prediction design exists by name - * @param id The prediction design id - * @param name The prediction design name - * @return 1 if a prediction design with the given name and id exist in WebAPI and 0 otherwise - */ + public List getAnalysisList() { + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return StreamSupport + .stream(service.getAnalysisList().spliterator(), false) + .map(analysis -> { + CommonAnalysisDTO dto = conversionService.convert(analysis, CommonAnalysisDTO.class); + permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); + return dto; + }) + .collect(Collectors.toList()); + } else { + return StreamSupport + .stream(service.getAnalysisList().spliterator(), false) + .filter(candidateAnalysis -> permissionService.hasReadAccess(candidateAnalysis)) + .map(analysis -> { + CommonAnalysisDTO dto = conversionService.convert(analysis, CommonAnalysisDTO.class); + permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); + return dto; + }) + .collect(Collectors.toList()); + } + } + + + /** + * Check to see if a prediction design exists by name + * + * @summary Prediction design exists by name + * @param id The prediction design id + * @param name The prediction design name + * @return 1 if a prediction design with the given name and id exist in WebAPI and 0 otherwise + */ @GET @Path("/{id}/exists") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/ohdsi/webapi/security/PermissionController.java b/src/main/java/org/ohdsi/webapi/security/PermissionController.java index 7a1bd26c4c..25ae6cedcb 100644 --- a/src/main/java/org/ohdsi/webapi/security/PermissionController.java +++ b/src/main/java/org/ohdsi/webapi/security/PermissionController.java @@ -83,26 +83,31 @@ public List listAccessesForEntity(@QueryParam("roleSearch") String role } /** - * Get entity role access information + * Get roles that have a permission type (READ/WRITE) to entity * - * @summary Get entity role information + * @summary Get roles that have a specific permission (READ/WRITE) for the entity * @param entityType The entity type * @param entityId The entity ID - * @return The list of roles + * @return The list of permissions for the permission type * @throws Exception */ @GET - @Path("/access/{entityType}/{entityId}") + @Path("/access/{entityType}/{entityId}/{permType}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public List listAccessesForEntity( + public List listAccessesForEntityByPermType( @PathParam("entityType") EntityType entityType, - @PathParam("entityId") Integer entityId + @PathParam("entityId") Integer entityId, + @PathParam("permType") String permType ) throws Exception { permissionService.checkCommonEntityOwnership(entityType, entityId); - - Set permissionTemplates = permissionService.getTemplatesForType(entityType, AccessType.WRITE).keySet(); + Set permissionTemplates = null; + if (permType == "WRITE") { + permissionTemplates = permissionService.getTemplatesForType(entityType, AccessType.WRITE).keySet(); + } else { + permissionTemplates = permissionService.getTemplatesForType(entityType, AccessType.READ).keySet(); + } List permissions = permissionTemplates .stream() diff --git a/src/main/java/org/ohdsi/webapi/security/PermissionService.java b/src/main/java/org/ohdsi/webapi/security/PermissionService.java index b3ec2f7e5c..3f11454db6 100644 --- a/src/main/java/org/ohdsi/webapi/security/PermissionService.java +++ b/src/main/java/org/ohdsi/webapi/security/PermissionService.java @@ -136,6 +136,8 @@ public Map getPermissionTemplates(EntityPermissionSchema permiss switch (accessType) { case WRITE: return permissionSchema.getWritePermissions(); + case READ: + return permissionSchema.getReadPermissions(); default: throw new UnsupportedOperationException(); } @@ -227,6 +229,25 @@ public List getRolesHavingPermissions(EntityType entityType, Number id) return roles; } + public List getRolesHavingReadPermissions(EntityType entityType, Number id) { + Set permissionTemplates = getTemplatesForType(entityType, AccessType.READ).keySet(); + preparePermissionCache(entityType, permissionTemplates); + + List permissions = permissionTemplates.stream() + .map(pt -> getPermission(pt, id)) + .collect(Collectors.toList()); + int fitCount = permissions.size(); + Map roleMap = permissions.stream() + .filter(p -> permissionCache.get().get(entityType).get(p) != null) + .flatMap(p -> permissionCache.get().get(entityType).get(p).stream()) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + List roles = roleMap.entrySet().stream() + .filter(es -> es.getValue() == fitCount) + .map(es -> es.getKey()) + .collect(Collectors.toList()); + return roles; + } + public void clearPermissionCache() { this.permissionCache.set(new ConcurrentHashMap<>()); } @@ -237,7 +258,9 @@ public boolean hasWriteAccess(CommonEntity entity) { try { String login = this.permissionManager.getSubjectName(); UserSimpleAuthorizationInfo authorizationInfo = this.permissionManager.getAuthorizationInfo(login); - if (!Objects.equals(authorizationInfo.getUserId(), entity.getCreatedBy().getId())) { + if (Objects.equals(authorizationInfo.getUserId(), entity.getCreatedBy().getId())) { + hasAccess = true; // the role is the one that created the artifact + } else { EntityType entityType = entityPermissionSchemaResolver.getEntityType(entity.getClass()); List roles = getRolesHavingPermissions(entityType, entity.getId()); @@ -255,11 +278,44 @@ public boolean hasWriteAccess(CommonEntity entity) { return hasAccess; } + + public boolean hasReadAccess(CommonEntity entity) { + boolean hasAccess = false; + if (securityEnabled && entity.getCreatedBy() != null) { + try { + String login = this.permissionManager.getSubjectName(); + UserSimpleAuthorizationInfo authorizationInfo = this.permissionManager.getAuthorizationInfo(login); + if (Objects.equals(authorizationInfo.getUserId(), entity.getCreatedBy().getId())){ + hasAccess = true; // the role is the one that created the artifact + } else { + EntityType entityType = entityPermissionSchemaResolver.getEntityType(entity.getClass()); + + List roles = getRolesHavingReadPermissions(entityType, entity.getId()); + + Collection userRoles = authorizationInfo.getRoles(); + hasAccess = roles.stream() + .anyMatch(r -> userRoles.stream() + .anyMatch(re -> re.equals(r.getName()))); + } + } catch (Exception e) { + logger.error("Error getting user roles and permissions", e); + throw new RuntimeException(e); + } + } + return hasAccess; + } + public void fillWriteAccess(CommonEntity entity, CommonEntityDTO entityDTO) { if (securityEnabled && entity.getCreatedBy() != null) { entityDTO.setHasWriteAccess(hasWriteAccess(entity)); } } + + public void fillReadAccess(CommonEntity entity, CommonEntityDTO entityDTO) { + if (securityEnabled && entity.getCreatedBy() != null) { + entityDTO.setHasReadAccess(hasReadAccess(entity)); + } + } public boolean isSecurityEnabled() { return this.securityEnabled; diff --git a/src/main/java/org/ohdsi/webapi/security/model/CohortCharacterizationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/CohortCharacterizationPermissionSchema.java index 7768187da0..f6ea10012a 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/CohortCharacterizationPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/CohortCharacterizationPermissionSchema.java @@ -13,8 +13,19 @@ public class CohortCharacterizationPermissionSchema extends EntityPermissionSche put("cohort-characterization:%s:delete", "Delete Cohort Characterization with ID = %s"); }}; + private static Map readPermissions = new HashMap() {{ + put("cohort-characterization:%s:get", "Get cohort characterization"); + put("cohort-characterization:%s:generation:get", "Get cohort characterization generations"); + put("cohort-characterization:generation:*:get", "Get cohort characterization generation"); + put("cohort-characterization:design:get", "cohort-characterization:design:get"); + put("cohort-characterization:%s:design:get", "Get cohort characterization design"); + put("cohort-characterization:design:%s:get", "view cohort characterization with id %s"); + put("cohort-characterization:%s:version:get", "Get list of characterization versions"); + put("cohort-characterization:%s:version:*:get", "Get list of characterization version"); + }}; + public CohortCharacterizationPermissionSchema() { - super(EntityType.COHORT_CHARACTERIZATION, new HashMap<>(), writePermissions); + super(EntityType.COHORT_CHARACTERIZATION, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/CohortDefinitionPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/CohortDefinitionPermissionSchema.java index 5c7f87c50a..bb6781ae0a 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/CohortDefinitionPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/CohortDefinitionPermissionSchema.java @@ -14,8 +14,17 @@ public class CohortDefinitionPermissionSchema extends EntityPermissionSchema { put("cohortdefinition:%s:check:post", "Fix Cohort Definition with ID = %s"); }}; + private static Map readPermissions = new HashMap() {{ + put("cohortdefinition:%s:get", "Get Cohort Definition by ID"); + put("cohortdefinition:%s:info:get",""); + + put("cohortdefinition:%s:version:get", "Get list of cohort versions"); + put("cohortdefinition:%s:version:*:get", "Get cohort version"); + } + }; + public CohortDefinitionPermissionSchema() { - super(EntityType.COHORT_DEFINITION, new HashMap<>(), writePermissions); + super(EntityType.COHORT_DEFINITION, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java index 846cbc9b0c..66b4b1a4b2 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ConceptSetPermissionSchema.java @@ -14,8 +14,14 @@ public class ConceptSetPermissionSchema extends EntityPermissionSchema { put("conceptset:%s:delete", "Delete Concept Set with ID = %s"); }}; + private static Map readPermissions = new HashMap() {{ + put("conceptset:%s:get", "view conceptset definition with id %s"); + put("conceptset:%s:expression:get", "Resolve concept set %s expression"); + put("conceptset:%s:version:*:expression:get", "Get expression for concept set %s items for default source"); + }}; + public ConceptSetPermissionSchema() { - super(EntityType.CONCEPT_SET, new HashMap<>(), writePermissions); + super(EntityType.CONCEPT_SET, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/EstimationPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/EstimationPermissionSchema.java index 416b049fa6..0df160ee52 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EstimationPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EstimationPermissionSchema.java @@ -13,8 +13,19 @@ public class EstimationPermissionSchema extends EntityPermissionSchema { put("estimation:%s:delete", "Delete Estimation with ID=%s"); }}; - public EstimationPermissionSchema() { + private static Map readPermissions = new HashMap() {{ + put("estimation:%s:get", "Get Estimation instance"); + put("estimation:%s:generation:get", "View Estimation Generations"); + put("estimation:%s:copy:get", "Copy Estimation instance"); + put("estimation:%s:download:get", "Download Estimation package"); + put("estimation:%s:export:get", "Export Estimation"); + put("estimation:%s:generation:get", "View Estimation Generations"); + put("comparativecohortanalysis:%s:get","Get estimation"); + } + }; - super(EntityType.ESTIMATION, new HashMap<>(), writePermissions); + + public EstimationPermissionSchema() { + super(EntityType.ESTIMATION, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/FeatureAnalysisPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/FeatureAnalysisPermissionSchema.java index 0090a45b3b..b36ea20b64 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/FeatureAnalysisPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/FeatureAnalysisPermissionSchema.java @@ -13,8 +13,14 @@ public class FeatureAnalysisPermissionSchema extends EntityPermissionSchema { put("feature-analysis:%s:delete", "Delete Feature Analysis with ID = %s"); }}; + private static Map readPermissions = new HashMap() {{ + put("feature-analysis:%s:get", "get feature analysis"); + put("feature-analysis:aggregates:get", "feature-analysis:aggregates:get"); + } + }; + public FeatureAnalysisPermissionSchema() { - super(EntityType.FE_ANALYSIS, new HashMap<>(), writePermissions); + super(EntityType.FE_ANALYSIS, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/IncidenceRatePermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/IncidenceRatePermissionSchema.java index 1c336a3ff2..57441e5edf 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/IncidenceRatePermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/IncidenceRatePermissionSchema.java @@ -15,8 +15,18 @@ public class IncidenceRatePermissionSchema extends EntityPermissionSchema { put("ir:%s:delete", "Delete Incidence Rate with ID=%s"); }}; + private static Map readPermissions = new HashMap() {{ + put("ir:%s:get", "view list of incident rates"); + put("ir:%s:version:get", "Get list of IR analsis versions"); + put("ir:%s:version:*:get", "Get IR analysis version"); + put("ir:%s:copy:get","Copy incidence rate"); + put("ir:%s:info:get","Get IR info"); + put("ir:%s:design:get","Export Incidence Rates design"); + } + }; + public IncidenceRatePermissionSchema() { - super(EntityType.INCIDENCE_RATE, new HashMap<>(), writePermissions); + super(EntityType.INCIDENCE_RATE, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/PathwayAnalysisPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/PathwayAnalysisPermissionSchema.java index 6c19b76c09..2f6f30ec63 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/PathwayAnalysisPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/PathwayAnalysisPermissionSchema.java @@ -14,8 +14,19 @@ public class PathwayAnalysisPermissionSchema extends EntityPermissionSchema { put("pathway-analysis:%s:delete", "Delete Pathway Analysis with ID = %s"); }}; + private static Map readPermissions = new HashMap() {{ + put("pathway-analysis:%s:get", "Get Pathways Analysis instance"); + put("pathway-analysis:%s:generation:get", "Get Pathways Analysis generations list"); + put("pathway-analysis:generation:*:get", "Get Pathways Analysis generation instance"); + put("pathway-analysis:generation:*:result:get", "Get Pathways Analysis generation results"); + put("pathway-analysis:generation:*:design:get", "Get Pathways Analysis generation design"); + put("pathway-analysis:%s:version:get", "Get list of pathway analysis versions"); + put("pathway-analysis:%s:version:*:get", "Get pathway analysis version"); + } + }; + public PathwayAnalysisPermissionSchema() { - super(EntityType.PATHWAY_ANALYSIS, new HashMap<>(), writePermissions); + super(EntityType.PATHWAY_ANALYSIS, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/PredictionPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/PredictionPermissionSchema.java index ad34f12425..d2e3cc458d 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/PredictionPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/PredictionPermissionSchema.java @@ -13,8 +13,19 @@ public class PredictionPermissionSchema extends EntityPermissionSchema { put("prediction:%s:delete", "Delete Estimation with ID=%s"); }}; + private static Map readPermissions = new HashMap() {{ + put("prediction:%s:get", "Get Prediction instance"); + put("prediction:%s:copy:get", "Copy Prediction instance"); + put("prediction:%s:download:get", "Download Prediction package"); + put("prediction:%s:export:get", "Export Prediction"); + put("prediction:%s:generation:get", "View Prediction Generations"); + put("prediction:%s:exists:get", "Check name uniqueness of prediction"); + put("plp:%s:get", "Get population level prediction"); + } + }; + public PredictionPermissionSchema() { - super(EntityType.PREDICTION, new HashMap<>(), writePermissions); + super(EntityType.PREDICTION, readPermissions, writePermissions); } } diff --git a/src/main/java/org/ohdsi/webapi/security/model/ReusablePermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/ReusablePermissionSchema.java index 78803dc01e..6ee5a940a6 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/ReusablePermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/ReusablePermissionSchema.java @@ -13,6 +13,13 @@ public class ReusablePermissionSchema extends EntityPermissionSchema { put("reusable:%s:put", "Update reusable"); }}; + private static Map readPermissions = new HashMap() {{ + put("reusable:%s:get", "view reusable with id %s"); + put("reusable:%s:expression:get", "Resolve reusable %s expression"); + put("reusable:%s:version:*:get", "Get expression for reusable %s items for default source"); + } + }; + public ReusablePermissionSchema() { super(EntityType.REUSABLE, new HashMap<>(), writePermissions); diff --git a/src/main/java/org/ohdsi/webapi/security/model/TagPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/TagPermissionSchema.java index e68747570b..58476a4fe9 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/TagPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/TagPermissionSchema.java @@ -13,6 +13,12 @@ public class TagPermissionSchema extends EntityPermissionSchema { put("tag:%s:put", "Update tag"); }}; + private static Map readPermissions = new HashMap() {{ + put("tag:get", "view tag with id %s"); + put("tag:search:get", "Resolve tag %s expression"); + } + }; + public TagPermissionSchema() { super(EntityType.TAG, new HashMap<>(), writePermissions); diff --git a/src/main/java/org/ohdsi/webapi/service/CohortDefinitionService.java b/src/main/java/org/ohdsi/webapi/service/CohortDefinitionService.java index 976389eb54..e580a3eb56 100644 --- a/src/main/java/org/ohdsi/webapi/service/CohortDefinitionService.java +++ b/src/main/java/org/ohdsi/webapi/service/CohortDefinitionService.java @@ -86,6 +86,7 @@ import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.job.builder.SimpleJobBuilder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.RowMapper; @@ -204,6 +205,9 @@ public class CohortDefinitionService extends AbstractDaoService implements HasTa @Autowired private VersionService versionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + private final MarkdownRender markdownPF = new MarkdownRender(); private final List extensions = Arrays.asList(TablesExtension.create()); @@ -406,14 +410,26 @@ public GenerateSqlResult generateSql(GenerateSqlRequest request) { @Transactional public List getCohortDefinitionList() { List definitions = cohortDefinitionRepository.list(); - - return definitions.stream() - .map(def -> { - CohortMetadataDTO dto = conversionService.convert(def, CohortMetadataImplDTO.class); - permissionService.fillWriteAccess(def, dto); - return dto; - }) - .collect(Collectors.toList()); + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return definitions.stream() + .map(def -> { + CohortMetadataDTO dto = conversionService.convert(def, CohortMetadataImplDTO.class); + permissionService.fillWriteAccess(def, dto); + permissionService.fillReadAccess(def, dto); + return dto; + }) + .collect(Collectors.toList()); + } else { // filter out cohortdefinitions that the user does not have read access to + return definitions.stream() + .filter(candidateCohortDef -> permissionService.hasReadAccess(candidateCohortDef)) + .map(def -> { + CohortMetadataDTO dto = conversionService.convert(def, CohortMetadataImplDTO.class); + permissionService.fillWriteAccess(def, dto); + permissionService.fillReadAccess(def, dto); + return dto; + }) + .collect(Collectors.toList()); + } } /** diff --git a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java index 1186a22445..76aceb5a10 100644 --- a/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java +++ b/src/main/java/org/ohdsi/webapi/service/ConceptSetService.java @@ -58,6 +58,7 @@ import org.ohdsi.webapi.versioning.service.VersionService; import org.ohdsi.webapi.vocabulary.Concept; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Component; @@ -103,6 +104,9 @@ public class ConceptSetService extends AbstractDaoService implements HasTags versionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + public static final String COPY_NAME = "copyName"; /** @@ -131,15 +135,28 @@ public ConceptSetDTO getConceptSet(@PathParam("id") final int id) { @Path("/") @Produces(MediaType.APPLICATION_JSON) public Collection getConceptSets() { - return getTransactionTemplate().execute(transactionStatus -> - StreamSupport.stream(getConceptSetRepository().findAll().spliterator(), false) - .map(conceptSet -> { - ConceptSetDTO dto = conversionService.convert(conceptSet, ConceptSetDTO.class); - permissionService.fillWriteAccess(conceptSet, dto); - return dto; - }) - .collect(Collectors.toList()) - ); + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return getTransactionTemplate().execute( + transactionStatus -> StreamSupport.stream(getConceptSetRepository().findAll().spliterator(), false) + .map(conceptSet -> { + ConceptSetDTO dto = conversionService.convert(conceptSet, ConceptSetDTO.class); + permissionService.fillWriteAccess(conceptSet, dto); + permissionService.fillReadAccess(conceptSet, dto); + return dto; + }) + .collect(Collectors.toList())); + } else { // filter out conceptsets that the user does not have read access to + return getTransactionTemplate().execute( + transactionStatus -> StreamSupport.stream(getConceptSetRepository().findAll().spliterator(), false) + .filter(candidateConceptSet -> permissionService.hasReadAccess(candidateConceptSet)) + .map(conceptSet -> { + ConceptSetDTO dto = conversionService.convert(conceptSet, ConceptSetDTO.class); + permissionService.fillWriteAccess(conceptSet, dto); + permissionService.fillReadAccess(conceptSet, dto); + return dto; + }) + .collect(Collectors.toList())); + } } /** diff --git a/src/main/java/org/ohdsi/webapi/service/IRAnalysisService.java b/src/main/java/org/ohdsi/webapi/service/IRAnalysisService.java index ea66cdce4c..9ef34897f6 100644 --- a/src/main/java/org/ohdsi/webapi/service/IRAnalysisService.java +++ b/src/main/java/org/ohdsi/webapi/service/IRAnalysisService.java @@ -85,6 +85,7 @@ import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.job.builder.SimpleJobBuilder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; @@ -141,6 +142,9 @@ public class IRAnalysisService extends AbstractDaoService implements private final IRAnalysisQueryBuilder queryBuilder; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + @Autowired private IncidenceRateAnalysisRepository irAnalysisRepository; @@ -341,17 +345,32 @@ private String getStrataTreemapData(int analysisId, int targetId, int outcomeId, @Override public List getIRAnalysisList() { - - return getTransactionTemplate().execute(transactionStatus -> { - Iterable analysisList = this.irAnalysisRepository.findAll(); - return StreamSupport.stream(analysisList.spliterator(), false) - .map(analysis -> { + if (defaultGlobalReadPermissions == true) { // don't filter based on read permissions + return getTransactionTemplate().execute(transactionStatus -> { + Iterable analysisList = this.irAnalysisRepository.findAll(); + return StreamSupport.stream(analysisList.spliterator(), false) + .map(analysis -> { IRAnalysisShortDTO dto = conversionService.convert(analysis, IRAnalysisShortDTO.class); permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); return dto; }) - .collect(Collectors.toList()); - }); + .collect(Collectors.toList()); + }); + } else { // filter out entities that the user does not have read permissions to view + return getTransactionTemplate().execute(transactionStatus -> { + Iterable analysisList = this.irAnalysisRepository.findAll(); + return StreamSupport.stream(analysisList.spliterator(), false) + .filter(candidateIRAnalysis -> permissionService.hasReadAccess(candidateIRAnalysis)) + .map(analysis -> { + IRAnalysisShortDTO dto = conversionService.convert(analysis, IRAnalysisShortDTO.class); + permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); + return dto; + }) + .collect(Collectors.toList()); + }); + } } @Override diff --git a/src/main/java/org/ohdsi/webapi/service/dto/CommonEntityDTO.java b/src/main/java/org/ohdsi/webapi/service/dto/CommonEntityDTO.java index 5a33780593..287894aef9 100644 --- a/src/main/java/org/ohdsi/webapi/service/dto/CommonEntityDTO.java +++ b/src/main/java/org/ohdsi/webapi/service/dto/CommonEntityDTO.java @@ -17,7 +17,9 @@ public abstract class CommonEntityDTO implements CommonDTO { private Date createdDate; @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Date modifiedDate; + private boolean hasWriteAccess; + private boolean hasReadAccess; public UserDTO getCreatedBy() { return createdBy; @@ -58,4 +60,12 @@ public boolean isHasWriteAccess() { public void setHasWriteAccess(boolean hasWriteAccess) { this.hasWriteAccess = hasWriteAccess; } + + public boolean isHasReadAccess() { + return hasReadAccess; + } + + public void setHasReadAccess(boolean hasReadAccess) { + this.hasReadAccess = hasReadAccess; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cbc3823116..cd1afb2013 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -107,6 +107,7 @@ csrf.disable=true sparql.endpoint=http://virtuoso.ohdsi.org:8890/sparql?default-graph-uri=&query= +security.defaultGlobalReadPermissions=${security.defaultGlobalReadPermissions} security.provider=${security.provider} security.cors.enabled=${security.cors.enabled} security.token.expiration=${security.token.expiration}