Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,10 @@ public void unassignTag(@PathVariable("id") final long id, @PathVariable("tagId"
*/
@PostMapping(value = "/{id}/protectedtag")
@Transactional
@PreAuthorize("isOwner(#id, COHORT_CHARACTERIZATION) or isPermitted('admin:tags') or isPermitted('write:cohort-characterization') or hasEntityAccess(#id, COHORT_CHARACTERIZATION, WRITE)")
@PreAuthorize("""
(isOwner(#id, COHORT_CHARACTERIZATION) or isPermitted('write:cohort-characterization') or hasEntityAccess(#id, COHORT_CHARACTERIZATION, WRITE))
and isPermitted('admin:tags')
""")
public void assignPermissionProtectedTag(@PathVariable("id") final long id, @RequestBody final int tagId) {
service.assignTag(id, tagId);
}
Expand All @@ -513,7 +516,10 @@ public void assignPermissionProtectedTag(@PathVariable("id") final long id, @Req
*/
@DeleteMapping(value = "/{id}/protectedtag/{tagId}")
@Transactional
@PreAuthorize("isOwner(#id, COHORT_CHARACTERIZATION) or isPermitted('admin:tags') or isPermitted('write:cohort-characterization') or hasEntityAccess(#id, COHORT_CHARACTERIZATION, WRITE)")
@PreAuthorize("""
(isOwner(#id, COHORT_CHARACTERIZATION) or isPermitted('write:cohort-characterization') or hasEntityAccess(#id, COHORT_CHARACTERIZATION, WRITE))
and isPermitted('admin:tags')
""")
public void unassignPermissionProtectedTag(@PathVariable("id") final long id, @PathVariable("tagId") final int tagId) {
service.unassignTag(id, tagId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,10 @@ public void unassignTag(@PathVariable("id") final Integer id, @PathVariable("tag
*/
@PostMapping(value = "/{id}/protectedtag", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@PreAuthorize("isOwner(#id, COHORT_DEFINITION) or isPermitted('admin:tags') or isPermitted('write:cohort-definition') or hasEntityAccess(#id, COHORT_DEFINITION, WRITE)")
@PreAuthorize("""
(isOwner(#id, COHORT_DEFINITION) or isPermitted('write:cohort-definition') or hasEntityAccess(#id, COHORT_DEFINITION, WRITE))
and isPermitted('admin:tags')
""")
public void assignPermissionProtectedTag(@PathVariable("id") final int id, @RequestBody final int tagId) {
assignTag(id, tagId);
}
Expand All @@ -1076,7 +1079,10 @@ public void assignPermissionProtectedTag(@PathVariable("id") final int id, @Requ
*/
@DeleteMapping(value = "/{id}/protectedtag/{tagId}", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@PreAuthorize("isOwner(#id, COHORT_DEFINITION) or isPermitted('admin:tags') or isPermitted('write:cohort-definition') or hasEntityAccess(#id, COHORT_DEFINITION, WRITE)")
@PreAuthorize("""
(isOwner(#id, COHORT_DEFINITION) or isPermitted('write:cohort-definition') or hasEntityAccess(#id, COHORT_DEFINITION, WRITE))
and isPermitted('admin:tags')
""")
public void unassignPermissionProtectedTag(@PathVariable("id") final int id, @PathVariable("tagId") final int tagId) {
unassignTag(id, tagId);
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/org/ohdsi/webapi/conceptset/ConceptSetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,10 @@ public void unassignTag(
*/
@PostMapping(value = "/{id}/protectedtag", consumes = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@PreAuthorize("isOwner(#id, CONCEPT_SET) or isPermitted('write:conceptset') or isPermitted('admin:tags') or hasEntityAccess(#id, CONCEPT_SET, WRITE)")
@PreAuthorize("""
(isOwner(#id, CONCEPT_SET) or isPermitted('write:conceptset') or hasEntityAccess(#id, CONCEPT_SET, WRITE))
and isPermitted('admin:tags')
""")
public void assignPermissionProtectedTag(
@PathVariable("id") final int id,
@RequestBody final int tagId) {
Expand All @@ -735,7 +738,10 @@ public void assignPermissionProtectedTag(
@DeleteMapping(value = "/{id}/protectedtag/{tagId}")
@Transactional
@CacheEvict(cacheNames = CachingSetup.CONCEPT_SET_LIST_CACHE, allEntries = true)
@PreAuthorize("isOwner(#id, CONCEPT_SET) or isPermitted('write:conceptset') or isPermitted('admin:tags') or hasEntityAccess(#id, CONCEPT_SET, WRITE)")
@PreAuthorize("""
(isOwner(#id, CONCEPT_SET) or isPermitted('write:conceptset') or hasEntityAccess(#id, CONCEPT_SET, WRITE))
and isPermitted('admin:tags')
""")
public void unassignPermissionProtectedTag(
@PathVariable("id") final int id,
@PathVariable("tagId") final int tagId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;

import java.util.List;

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/ohdsi/webapi/ircalc/IRAnalysisService.java
Original file line number Diff line number Diff line change
Expand Up @@ -804,14 +804,14 @@ public void unassignTag(final Integer id, final int tagId) {

@Override
@Transactional
@PreAuthorize("isOwner(#id, INCIDENCE_RATE) or isPermitted('admin:tags') or isPermitted('write:incidence') or hasEntityAccess(#id, INCIDENCE_RATE, WRITE)")
@PreAuthorize("(isOwner(#id, INCIDENCE_RATE) or isPermitted('write:incidence') or hasEntityAccess(#id, INCIDENCE_RATE, WRITE)) and isPermitted('admin:tags')")
public void assignPermissionProtectedTag(final int id, final int tagId) {
assignTag(id, tagId);
}

@Override
@Transactional
@PreAuthorize("isOwner(#id, INCIDENCE_RATE) or isPermitted('admin:tags') or isPermitted('write:incidence') or hasEntityAccess(#id, INCIDENCE_RATE, WRITE)")
@PreAuthorize("(isOwner(#id, INCIDENCE_RATE) or isPermitted('write:incidence') or hasEntityAccess(#id, INCIDENCE_RATE, WRITE)) and isPermitted('admin:tags')")
public void unassignPermissionProtectedTag(final int id, final int tagId) {
unassignTag(id, tagId);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/ohdsi/webapi/pathway/PathwayController.java
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ public void unassignTag(@PathVariable("id") final int id, @PathVariable("tagId")
*/
@PostMapping(value = "/{id}/protectedtag", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@PreAuthorize("isOwner(#id, PATHWAY_ANALYSIS) or isPermitted('admin:tags') or isPermitted('write:pathway') or hasEntityAccess(#id, PATHWAY_ANALYSIS, WRITE)")
@PreAuthorize("(isOwner(#id, PATHWAY_ANALYSIS) or isPermitted('write:pathway') or hasEntityAccess(#id, PATHWAY_ANALYSIS, WRITE)) and isPermitted('admin:tags')")
public void assignPermissionProtectedTag(@PathVariable("id") final int id, @RequestBody final int tagId) {
pathwayService.assignTag(id, tagId);
}
Expand All @@ -513,7 +513,7 @@ public void assignPermissionProtectedTag(@PathVariable("id") final int id, @Requ
*/
@DeleteMapping(value = "/{id}/protectedtag/{tagId}")
@Transactional
@PreAuthorize("isOwner(#id, PATHWAY_ANALYSIS) or isPermitted('admin:tags') or isPermitted('write:pathway') or hasEntityAccess(#id, PATHWAY_ANALYSIS, WRITE)")
@PreAuthorize("(isOwner(#id, PATHWAY_ANALYSIS) or isPermitted('write:pathway') or hasEntityAccess(#id, PATHWAY_ANALYSIS, WRITE)) and isPermitted('admin:tags')")
public void unassignPermissionProtectedTag(@PathVariable("id") final int id, @PathVariable("tagId") final int tagId) {
pathwayService.unassignTag(id, tagId);
}
Expand Down
37 changes: 21 additions & 16 deletions src/main/java/org/ohdsi/webapi/source/SourceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,26 @@
import org.jasypt.encryption.pbe.PBEStringEncryptor;
import org.jasypt.properties.PropertyValueEncryptionUtils;
import org.ohdsi.sql.SqlTranslate;
import org.ohdsi.webapi.arachne.logging.event.AddDataSourceEvent;
import org.ohdsi.webapi.arachne.logging.event.ChangeDataSourceEvent;
import org.ohdsi.webapi.arachne.logging.event.DeleteDataSourceEvent;
import org.ohdsi.webapi.common.DBMSType;
import org.ohdsi.webapi.common.SourceMapKey;
import org.ohdsi.webapi.exception.SourceDuplicateKeyException;
import org.ohdsi.webapi.security.authz.AuthorizationService;
import org.ohdsi.webapi.security.authz.RoleEntity;
import org.ohdsi.webapi.security.authz.UserEntity;
import org.ohdsi.webapi.security.identity.WebApiPrincipal;
import org.ohdsi.webapi.security.authz.access.AccessType;
import org.ohdsi.webapi.security.authz.access.EntityType;
import org.ohdsi.webapi.service.AbstractDaoService;
import org.ohdsi.webapi.service.VocabularyService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -47,9 +45,6 @@

import org.ohdsi.webapi.util.CacheHelper;

/**
* TODO: Need to add permission annotations to these methods
*/
@RestController
@RequestMapping("/source")
@Transactional
Expand Down Expand Up @@ -90,24 +85,21 @@ public void customize(CacheManager cacheManager) {

private final GenericConversionService conversionService;
private final VocabularyService vocabularyService;
private final ApplicationEventPublisher publisher;

public SourceService(SourceRepository sourceRepository,
SourceDaimonRepository sourceDaimonRepository,
AuthorizationService authorizationService,
JdbcTemplate jdbcTemplate,
PBEStringEncryptor defaultStringEncryptor,
GenericConversionService conversionService,
@Lazy VocabularyService vocabularyService,
ApplicationEventPublisher publisher) {
@Lazy VocabularyService vocabularyService) {
this.sourceRepository = sourceRepository;
this.sourceDaimonRepository = sourceDaimonRepository;
this.authorizationService = authorizationService;
this.jdbcTemplate = jdbcTemplate;
this.defaultStringEncryptor = defaultStringEncryptor;
this.conversionService = conversionService;
this.vocabularyService = vocabularyService;
this.publisher = publisher;
}

@EventListener(ApplicationReadyEvent.class)
Expand Down Expand Up @@ -205,6 +197,7 @@ public ResponseEntity<SourceInfo> getSource(@PathVariable("key") final String so
* @return
*/
@GetMapping(value = "/details/{sourceId}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isPermitted('admin:source')")
public ResponseEntity<SourceDetails> getSourceDetails(@PathVariable("sourceId") Integer sourceId) {
Source source = sourceRepository.findBySourceId(sourceId);
return ResponseEntity.ok(new SourceDetails(source));
Expand All @@ -221,6 +214,7 @@ public ResponseEntity<SourceDetails> getSourceDetails(@PathVariable("sourceId")
*/
@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@CacheEvict(cacheNames = CachingSetup.SOURCE_LIST_CACHE, allEntries = true)
@PreAuthorize("isPermitted('admin:source')")
public ResponseEntity<SourceInfo> createSource(
@RequestPart(value = "keyfile", required = false) MultipartFile keyfile,
@RequestPart("source") SourceRequest source) throws Exception {
Expand Down Expand Up @@ -258,9 +252,13 @@ public ResponseEntity<SourceInfo> createSource(
sourceEntity.setCreatedDate(new Date());
try {
Source saved = sourceRepository.saveAndFlush(sourceEntity);
// Sources have a role to grant write access to the source
AuthorizationService authSvc = this.getAuthorizationService();
RoleEntity sourceRole = authSvc.addRole(getSourceRoleName(saved.getSourceKey()), true);
// we are going to default giving this role 'WRITE', but could possibly define a read-only and a writeable roles in the future.
authSvc.grantEntityAccess(EntityType.SOURCE, Long.valueOf(saved.getId().longValue()),sourceRole.getId(), AccessType.WRITE);
invalidateCache();
SourceInfo sourceInfo = new SourceInfo(saved);
publisher.publishEvent(new AddDataSourceEvent(this, sourceEntity.getSourceId(), sourceEntity.getSourceName()));
return ResponseEntity.ok(sourceInfo);
} catch (PersistenceException ex) {
throw new SourceDuplicateKeyException("You cannot use this Source Key, please use different one");
Expand All @@ -280,6 +278,7 @@ public ResponseEntity<SourceInfo> createSource(
@PutMapping(value = "/{sourceId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
@CacheEvict(cacheNames = CachingSetup.SOURCE_LIST_CACHE, allEntries = true)
@PreAuthorize("isPermitted('admin:source')")
public ResponseEntity<SourceInfo> updateSource(
@PathVariable("sourceId") Integer sourceId,
@RequestPart(value = "keyfile", required = false) MultipartFile keyfile,
Expand Down Expand Up @@ -313,7 +312,6 @@ public ResponseEntity<SourceInfo> updateSource(
// Delete MUST be called after fetching user or source data to prevent autoflush (see DefaultPersistEventListener.onPersist)
sourceDaimonRepository.deleteAll(removed);
Source result = sourceRepository.save(updated);
publisher.publishEvent(new ChangeDataSourceEvent(this, updated.getSourceId(), updated.getSourceName()));
invalidateCache();
return ResponseEntity.ok(new SourceInfo(result));
} else {
Expand All @@ -332,12 +330,13 @@ public ResponseEntity<SourceInfo> updateSource(
@DeleteMapping("/{sourceId}")
@Transactional
@CacheEvict(cacheNames = CachingSetup.SOURCE_LIST_CACHE, allEntries = true)
@PreAuthorize("isPermitted('admin:source')")
public ResponseEntity<Void> delete(@PathVariable("sourceId") Integer sourceId) throws Exception {

Source source = sourceRepository.findBySourceId(sourceId);
if (source != null) {
sourceRepository.delete(source);
publisher.publishEvent(new DeleteDataSourceEvent(this, sourceId, source.getSourceName()));
// TODO: Deletes are 'soft-delete' so need to determine how to clean up the source's role.
invalidateCache();
return ResponseEntity.ok().build();
} else {
Expand All @@ -355,6 +354,7 @@ public ResponseEntity<Void> delete(@PathVariable("sourceId") Integer sourceId) t
*/
@GetMapping(value = "/connection/{key}", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional(noRollbackFor = CannotGetJdbcConnectionException.class)
@PreAuthorize("isPermitted('admin:source') or hasSourceAccess(#sourceKey, READ)")
public ResponseEntity<SourceInfo> checkConnectionEndpoint(@PathVariable("key") final String sourceKey) {
final Source source = findBySourceKey(sourceKey);
checkConnection(source);
Expand Down Expand Up @@ -389,6 +389,7 @@ public ResponseEntity<Map<SourceDaimon.DaimonType, SourceInfo>> getPriorityDaimo
*/
@PostMapping(value = "/{sourceKey}/daimons/{daimonType}/set-priority", produces = MediaType.APPLICATION_JSON_VALUE)
@CacheEvict(cacheNames = CachingSetup.SOURCE_LIST_CACHE, allEntries = true)
@PreAuthorize("isPermitted('admin:source')")
public ResponseEntity<Void> updateSourcePriority(
@PathVariable("sourceKey") final String sourceKey,
@PathVariable("daimonType") final String daimonTypeName) {
Expand Down Expand Up @@ -552,6 +553,10 @@ private boolean checkConnectionSafe(Source source) {
}
}

private String getSourceRoleName(String sourceKey) {
return String.format("Source user (%s)", sourceKey);
}

private class SortByKey implements Comparator<Source> {
private boolean isAscending;

Expand Down
9 changes: 5 additions & 4 deletions src/main/java/org/ohdsi/webapi/tag/TagService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.ohdsi.webapi.tag;

import org.apache.commons.lang3.StringUtils;
import org.ohdsi.webapi.security.authz.AuthorizationService;
import org.ohdsi.webapi.service.AbstractDaoService;
import org.ohdsi.webapi.tag.domain.Tag;
import org.ohdsi.webapi.tag.domain.TagInfo;
Expand Down Expand Up @@ -260,11 +261,11 @@ private void findParentGroup(Set<Tag> groups, Set<Integer> groupIds) {
*/
@GetMapping(value = "/assignmentPermissions", produces = MediaType.APPLICATION_JSON_VALUE)
public AssignmentPermissionsDTO getAssignmentPermissions() {
AuthorizationService authSvc = this.getAuthorizationService();
final AssignmentPermissionsDTO tagPermission = new AssignmentPermissionsDTO();
// TODO: determine what permission rules are being checked here for WebAPI 3.0 semantics.
// tagPermission.setAnyAssetMultiAssignPermitted(isAdmin());
// tagPermission.setCanAssignProtectedTags(!isSecured() || TagSecurityUtils.canAssingProtectedTags());
// tagPermission.setCanUnassignProtectedTags(!isSecured() || TagSecurityUtils.canUnassingProtectedTags());
tagPermission.setAnyAssetMultiAssignPermitted(authSvc.isPermitted("admin:tags"));
tagPermission.setCanAssignProtectedTags(authSvc.isPermitted("admin:tags"));
tagPermission.setCanUnassignProtectedTags(authSvc.isPermitted("admin:tags"));
return tagPermission;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,7 @@ FROM (
('admin:tools', 'Manage Tools'),
('admin:security', 'Manage users, roles, permissions'),
('admin:cache', 'View and manage chache functions'),
('admin:run-as', 'Run as another user'),
('create', 'Create any asset'),
('create:conceptset', 'Create concept sets'),
('create:cohort-definition', 'Create cohort definitions'),
Expand Down Expand Up @@ -2072,7 +2073,7 @@ INSERT INTO ${ohdsiSchema}.sec_user (id, login, name, origin)
VALUES (-1, 'anonymous', 'Anonymous', 'SYSTEM');

INSERT INTO ${ohdsiSchema}.sec_role (id, name, system_role)
VALUES (-1, 'anonymous', false);
VALUES (-1, 'anonymous', true);

INSERT INTO ${ohdsiSchema}.sec_user_role (id, user_id, role_id, origin)
VALUES (nextval('${ohdsiSchema}.sec_user_role_sequence'), -1, -1, 'SYSTEM');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ INSERT INTO ${ohdsiSchema}.sec_user (id, login, name, origin)
VALUES (-1, 'anonymous', 'Anonymous', 'SYSTEM');

INSERT INTO ${ohdsiSchema}.sec_role (id, name, system_role)
VALUES (-1, 'anonymous', false);
VALUES (-1, 'anonymous', true);

INSERT INTO ${ohdsiSchema}.sec_user_role (id, user_id, role_id, origin)
VALUES (nextval('${ohdsiSchema}.sec_user_role_sequence'), -1, -1, 'SYSTEM');
Expand Down Expand Up @@ -920,6 +920,7 @@ FROM (
('admin:tools', 'Manage Tools'),
('admin:security', 'Manage users, roles, permissions'),
('admin:cache', 'View and manage chache functions'),
('admin:run-as', 'Run as another user'),
('create', 'Create any asset'),
('create:conceptset', 'Create concept sets'),
('create:cohort-definition', 'Create cohort definitions'),
Expand Down Expand Up @@ -956,14 +957,16 @@ WITH perm_map AS (
/* ---------- ADMIN-Privs ---------- */
WHEN p.value ~ '^source:\*:(put|post|delete)$'
THEN 'admin:source'
WHEN p.value ~ '^(role:post|role:\*:(put|delete)|role:\*:users:\*:(put|delete)|role:\*:permissions:\*:(put|delete)|user:import:\*:(post|put|delete)|user:runas:post)$'
WHEN p.value ~ '^(role:post|role:\*:(put|delete)|role:\*:users:\*:(put|delete)|role:\*:permissions:\*:(put|delete)|user:import:\*:(post|put|delete))$'
THEN 'admin:security'
WHEN p.value ~ '^(tag:\*:(put|delete)|tag:(post|management)|tag:multi(Assign|Unassign):post)$'
THEN 'admin:tags'
WHEN p.value ~ '^(tool:\*:(put|delete)|tool:(post|put))$'
THEN 'admin:tools'
WHEN p.value ~ '^(cache:.*|cdmresults:clearcache:post)$'
THEN 'admin:cache'
THEN 'admin:cache'
WHEN p.value ~ '^(user:runas:post)$'
THEN 'admin:run-as'

/* ---------- CREATE ---------- */
WHEN p.value ~ '^conceptset:post$'
Expand Down
Loading