From 5393ed23240586876e00132e078afcdc44d689b3 Mon Sep 17 00:00:00 2001 From: Chris Knoll Date: Mon, 14 Aug 2023 10:30:09 -0400 Subject: [PATCH] Filter cohorts and concepts by permission (#2245) (#2301) The new property is called security.defaultGlobalReadPermissions which is true by default. Co-authored-by: Richard D Boyce, PhD --- .gitignore | 2 + Makefile | 55 ++++-------------- pom.xml | 5 ++ .../cohortcharacterization/CcController.java | 21 +++++-- .../cohortcharacterization/CcServiceImpl.java | 26 ++++++++- .../estimation/EstimationController.java | 9 ++- .../webapi/pathway/PathwayController.java | 6 +- .../webapi/pathway/PathwayServiceImpl.java | 25 +++++++- .../prediction/PredictionController.java | 12 +++- .../webapi/security/PermissionController.java | 42 ++++++++++---- .../webapi/security/PermissionService.java | 58 ++++++++++++++++++- ...ohortCharacterizationPermissionSchema.java | 13 ++++- .../CohortDefinitionPermissionSchema.java | 11 +++- .../model/ConceptSetPermissionSchema.java | 8 ++- .../model/EstimationPermissionSchema.java | 15 ++++- .../FeatureAnalysisPermissionSchema.java | 8 ++- .../model/IncidenceRatePermissionSchema.java | 12 +++- .../PathwayAnalysisPermissionSchema.java | 13 ++++- .../model/PredictionPermissionSchema.java | 13 ++++- .../model/ReusablePermissionSchema.java | 7 +++ .../security/model/TagPermissionSchema.java | 6 ++ .../service/CohortDefinitionService.java | 7 ++- .../webapi/service/ConceptSetService.java | 14 +++-- .../webapi/service/IRAnalysisService.java | 7 ++- .../webapi/service/dto/CommonEntityDTO.java | 10 ++++ src/main/resources/application.properties | 1 + 26 files changed, 319 insertions(+), 87 deletions(-) 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 5f8c9a199b..0bade7f5f7 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..d32f478a8d 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; @@ -151,11 +154,12 @@ 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; - }); + return service.getPage(pageable).map(entity -> { + CcShortDTO dto = convertCcToShortDto(entity); + permissionService.fillWriteAccess(entity, dto); + permissionService.fillReadAccess(entity, dto); + return dto; + }); } /** @@ -168,7 +172,12 @@ public Page list(@Pagination Pageable pageable) { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Page listDesign(@Pagination Pageable pageable) { - return service.getPageWithLinkedEntities(pageable).map(this::convertCcToDto); + return service.getPageWithLinkedEntities(pageable).map(entity -> { + CohortCharacterizationDTO dto = convertCcToDto(entity); + permissionService.fillWriteAccess(entity, dto); + permissionService.fillReadAccess(entity, dto); + return dto; + }); } /** diff --git a/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcServiceImpl.java b/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcServiceImpl.java index 455a741418..b4ee8849c1 100644 --- a/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcServiceImpl.java +++ b/src/main/java/org/ohdsi/webapi/cohortcharacterization/CcServiceImpl.java @@ -133,6 +133,9 @@ import static org.ohdsi.webapi.Constants.Params.JOB_AUTHOR; import static org.ohdsi.webapi.Constants.Params.JOB_NAME; import static org.ohdsi.webapi.Constants.Params.SOURCE_ID; +import org.ohdsi.webapi.security.PermissionService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.PageImpl; @Service @Transactional @@ -207,7 +210,12 @@ public class CcServiceImpl extends AbstractDaoService implements CcService, Gene private final GenericConversionService genericConversionService; private final VocabularyService vocabularyService; private VersionService versionService; + + private PermissionService permissionService; + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + private final Environment env; public CcServiceImpl( @@ -231,6 +239,7 @@ public CcServiceImpl( final JobInvalidator jobInvalidator, final VocabularyService vocabularyService, final VersionService versionService, + final PermissionService permissionService, @Qualifier("conversionService") final GenericConversionService genericConversionService, Environment env) { this.repository = ccRepository; @@ -251,6 +260,7 @@ public CcServiceImpl( this.eventPublisher = eventPublisher; this.jobInvalidator = jobInvalidator; this.vocabularyService = vocabularyService; + this.permissionService = permissionService; this.genericConversionService = genericConversionService; this.versionService = versionService; this.env = env; @@ -531,12 +541,24 @@ public CohortCharacterization findDesignByGenerationId(@CcGenerationId final Lon @Override public Page getPageWithLinkedEntities(final Pageable pageable) { - return repository.findAll(pageable, defaultEntityGraph); + return repository.findAll(pageable, defaultEntityGraph); + } @Override public Page getPage(final Pageable pageable) { - return repository.findAll(pageable); + List ccList = repository.findAll() + .stream().filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) + .collect(Collectors.toList()); + return getPageFromResults(pageable, ccList); + } + + private Page getPageFromResults(Pageable pageable, List results) { + // Calculate the start and end indices for the current page + int startIndex = pageable.getPageNumber() * pageable.getPageSize(); + int endIndex = Math.min(startIndex + pageable.getPageSize(), results.size()); + + return new PageImpl<>(results.subList(startIndex, endIndex), pageable, results.size()); } @Override diff --git a/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java b/src/main/java/org/ohdsi/webapi/estimation/EstimationController.java index 90c7b7fa33..485cc4f58f 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,11 +101,12 @@ public EstimationController(EstimationService service, @Path("/") @Produces(MediaType.APPLICATION_JSON) public List getAnalysisList() { - return StreamSupport.stream(service.getAnalysisList().spliterator(), false) + .filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) .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..4d7cd55dc6 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; @@ -156,13 +159,14 @@ 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); + permissionService.fillReadAccess(pa, dto); return dto; }); } + /** * Check that a pathway analysis name exists. diff --git a/src/main/java/org/ohdsi/webapi/pathway/PathwayServiceImpl.java b/src/main/java/org/ohdsi/webapi/pathway/PathwayServiceImpl.java index 12eb75f326..ed7fbae428 100644 --- a/src/main/java/org/ohdsi/webapi/pathway/PathwayServiceImpl.java +++ b/src/main/java/org/ohdsi/webapi/pathway/PathwayServiceImpl.java @@ -96,6 +96,10 @@ import static org.ohdsi.webapi.Constants.Params.JOB_NAME; import static org.ohdsi.webapi.Constants.Params.PATHWAY_ANALYSIS_ID; import static org.ohdsi.webapi.Constants.Params.SOURCE_ID; +import org.ohdsi.webapi.cohortcharacterization.domain.CohortCharacterizationEntity; +import org.ohdsi.webapi.security.PermissionService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.PageImpl; @Service @Transactional @@ -116,6 +120,11 @@ public class PathwayServiceImpl extends AbstractDaoService implements PathwaySer private final CohortDefinitionService cohortDefinitionService; private final VersionService versionService; + private PermissionService permissionService; + + @Value("#{'${security.defaultGlobalReadPermissions}'.equals(false)}") + private boolean defaultGlobalReadPermissions; + private final List STEP_COLUMNS = Arrays.asList(new String[]{"step_1", "step_2", "step_3", "step_4", "step_5", "step_6", "step_7", "step_8", "step_9", "step_10"}); private final EntityGraph defaultEntityGraph = EntityUtils.fromAttributePaths( @@ -142,7 +151,8 @@ public PathwayServiceImpl( @Qualifier("conversionService") GenericConversionService genericConversionService, StepBuilderFactory stepBuilderFactory, CohortDefinitionService cohortDefinitionService, - VersionService versionService) { + VersionService versionService, + PermissionService permissionService) { this.pathwayAnalysisRepository = pathwayAnalysisRepository; this.pathwayAnalysisGenerationRepository = pathwayAnalysisGenerationRepository; @@ -159,6 +169,7 @@ public PathwayServiceImpl( this.stepBuilderFactory = stepBuilderFactory; this.cohortDefinitionService = cohortDefinitionService; this.versionService = versionService; + this.permissionService = permissionService; SerializedPathwayAnalysisToPathwayAnalysisConverter.setConversionService(conversionService); } @@ -218,8 +229,18 @@ public PathwayAnalysisEntity importAnalysis(PathwayAnalysisEntity toImport) { @Override public Page getPage(final Pageable pageable) { + List pathwayList = pathwayAnalysisRepository.findAll(defaultEntityGraph) + .stream().filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) + .collect(Collectors.toList()); + return getPageFromResults(pageable, pathwayList); + } + + private Page getPageFromResults(Pageable pageable, List results) { + // Calculate the start and end indices for the current page + int startIndex = pageable.getPageNumber() * pageable.getPageSize(); + int endIndex = Math.min(startIndex + pageable.getPageSize(), results.size()); - return pathwayAnalysisRepository.findAll(pageable, defaultEntityGraph); + return new PageImpl<>(results.subList(startIndex, endIndex), pageable, results.size()); } @Override diff --git a/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java b/src/main/java/org/ohdsi/webapi/prediction/PredictionController.java index 67a35b7b55..bae37c4c12 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, @@ -94,12 +98,13 @@ public PredictionController(PredictionService service, @Path("/") @Produces(MediaType.APPLICATION_JSON) public List getAnalysisList() { - return StreamSupport .stream(service.getAnalysisList().spliterator(), false) + .filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) .map(analysis -> { CommonAnalysisDTO dto = conversionService.convert(analysis, CommonAnalysisDTO.class); permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); return dto; }) .collect(Collectors.toList()); @@ -107,11 +112,12 @@ public List getAnalysisList() { /** * 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 + * @return 1 if a prediction design with the given name and id exist in WebAPI + * and 0 otherwise */ @GET @Path("/{id}/exists") diff --git a/src/main/java/org/ohdsi/webapi/security/PermissionController.java b/src/main/java/org/ohdsi/webapi/security/PermissionController.java index 7a1bd26c4c..6471b75bbf 100644 --- a/src/main/java/org/ohdsi/webapi/security/PermissionController.java +++ b/src/main/java/org/ohdsi/webapi/security/PermissionController.java @@ -83,26 +83,28 @@ public List listAccessesForEntity(@QueryParam("roleSearch") String role } /** - * Get entity role access information - * - * @summary Get entity role information + * Get roles that have a permission type (READ/WRITE) to entity + * + * @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 - * @throws Exception + * @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") AccessType permType ) throws Exception { permissionService.checkCommonEntityOwnership(entityType, entityId); - - Set permissionTemplates = permissionService.getTemplatesForType(entityType, AccessType.WRITE).keySet(); + Set permissionTemplates = null; + permissionTemplates = permissionService.getTemplatesForType(entityType, permType).keySet(); List permissions = permissionTemplates .stream() @@ -114,6 +116,26 @@ public List listAccessesForEntity( return roles.stream().map(re -> conversionService.convert(re, RoleDTO.class)).collect(Collectors.toList()); } + /** + * Get roles that have a permission type (READ/WRITE) to entity + * + * @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 permissions for the permission type + * @throws Exception + */ + @GET + @Path("/access/{entityType}/{entityId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public List listAccessesForEntity( + @PathParam("entityType") EntityType entityType, + @PathParam("entityId") Integer entityId + ) throws Exception { + return listAccessesForEntityByPermType(entityType, entityId, AccessType.WRITE); + } /** * Grant group of permissions (READ / WRITE / ...) for the specified entity to the given role. diff --git a/src/main/java/org/ohdsi/webapi/security/PermissionService.java b/src/main/java/org/ohdsi/webapi/security/PermissionService.java index b3ec2f7e5c..5bb63729d3 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,11 +258,40 @@ 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()); + 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 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() @@ -260,6 +310,12 @@ public void fillWriteAccess(CommonEntity entity, CommonEntityDTO entityDTO) { 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..1c71755070 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,11 +410,12 @@ public GenerateSqlResult generateSql(GenerateSqlRequest request) { @Transactional public List getCohortDefinitionList() { List definitions = cohortDefinitionRepository.list(); - return definitions.stream() + .filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) .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..9b4143feea 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,17 @@ 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) + return getTransactionTemplate().execute( + transactionStatus -> StreamSupport.stream(getConceptSetRepository().findAll().spliterator(), false) + .filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) .map(conceptSet -> { ConceptSetDTO dto = conversionService.convert(conceptSet, ConceptSetDTO.class); permissionService.fillWriteAccess(conceptSet, dto); + permissionService.fillReadAccess(conceptSet, dto); return dto; }) - .collect(Collectors.toList()) - ); + .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..32d27257f0 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,13 +345,14 @@ 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) + .filter(!defaultGlobalReadPermissions ? entity -> permissionService.hasReadAccess(entity) : entity -> true) .map(analysis -> { IRAnalysisShortDTO dto = conversionService.convert(analysis, IRAnalysisShortDTO.class); permissionService.fillWriteAccess(analysis, dto); + permissionService.fillReadAccess(analysis, dto); return dto; }) .collect(Collectors.toList()); 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}