-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds ability for regular users to grant and revoke write permissions …
…for their owned assets (#1273) * Added services for listing, granting and revoking write permissions for an entity * Revert pom change * Fixed Source EntityType * Revert warmCaches * Renamed package * Added role suggest * Added access control to Cohort Characterizations * Added note * Migrations * fixes query group by fields
- Loading branch information
Showing
32 changed files
with
764 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package org.ohdsi.webapi.security; | ||
|
||
public enum AccessType { | ||
READ, | ||
WRITE | ||
} |
114 changes: 114 additions & 0 deletions
114
src/main/java/org/ohdsi/webapi/security/PermissionController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package org.ohdsi.webapi.security; | ||
|
||
import org.ohdsi.webapi.security.dto.AccessRequestDTO; | ||
import org.ohdsi.webapi.security.dto.RoleDTO; | ||
import org.ohdsi.webapi.security.model.EntityType; | ||
import org.ohdsi.webapi.shiro.Entities.RoleEntity; | ||
import org.ohdsi.webapi.shiro.PermissionManager; | ||
import org.springframework.core.convert.ConversionService; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.DELETE; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.POST; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.PathParam; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.QueryParam; | ||
import javax.ws.rs.core.MediaType; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
@Controller | ||
@Path(value = "/permission") | ||
@Transactional | ||
public class PermissionController { | ||
|
||
private final PermissionService permissionService; | ||
private final PermissionManager permissionManager; | ||
private final ConversionService conversionService; | ||
|
||
public PermissionController(PermissionService permissionService, PermissionManager permissionManager, ConversionService conversionService) { | ||
|
||
this.permissionService = permissionService; | ||
this.permissionManager = permissionManager; | ||
this.conversionService = conversionService; | ||
} | ||
|
||
@GET | ||
@Path("/access/suggest") | ||
@Consumes(MediaType.APPLICATION_JSON) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public List<RoleDTO> listAccessesForEntity(@QueryParam("roleSearch") String roleSearch) { | ||
|
||
List<RoleEntity> roles = permissionService.suggestRoles(roleSearch); | ||
return roles.stream().map(re -> conversionService.convert(re, RoleDTO.class)).collect(Collectors.toList()); | ||
} | ||
|
||
@GET | ||
@Path("/access/{entityType}/{entityId}") | ||
@Consumes(MediaType.APPLICATION_JSON) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public List<RoleDTO> listAccessesForEntity( | ||
@PathParam("entityType") EntityType entityType, | ||
@PathParam("entityId") Integer entityId | ||
) throws Exception { | ||
|
||
permissionService.checkCommonEntityOwnership(entityType, entityId); | ||
|
||
Set<String> permissionTemplates = permissionService.getTemplatesForType(entityType, AccessType.WRITE).keySet(); | ||
|
||
List<String> permissions = permissionTemplates | ||
.stream() | ||
.map(pt -> permissionService.getPermission(pt, entityId)) | ||
.collect(Collectors.toList()); | ||
|
||
List<RoleEntity> roles = permissionService.finaAllRolesHavingPermissions(permissions); | ||
|
||
return roles.stream().map(re -> conversionService.convert(re, RoleDTO.class)).collect(Collectors.toList()); | ||
} | ||
|
||
|
||
/** | ||
* Grant group of permissions (READ / WRITE / ...) for the specified entity to the given role. | ||
* Only owner of the entity can do that. | ||
*/ | ||
@POST | ||
@Path("/access/{entityType}/{entityId}/role/{roleId}") | ||
@Consumes(MediaType.APPLICATION_JSON) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public void grantEntityPermissionsForRole( | ||
@PathParam("entityType") EntityType entityType, | ||
@PathParam("entityId") Integer entityId, | ||
@PathParam("roleId") Long roleId, | ||
AccessRequestDTO accessRequestDTO | ||
) throws Exception { | ||
|
||
permissionService.checkCommonEntityOwnership(entityType, entityId); | ||
|
||
Map<String, String> permissionTemplates = permissionService.getTemplatesForType(entityType, accessRequestDTO.getAccessType()); | ||
|
||
RoleEntity role = permissionManager.getRole(roleId); | ||
permissionManager.addPermissionsFromTemplate(role, permissionTemplates, entityId.toString()); | ||
} | ||
|
||
@DELETE | ||
@Path("/access/{entityType}/{entityId}/role/{roleId}") | ||
@Consumes(MediaType.APPLICATION_JSON) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public void revokeEntityPermissionsFromRole( | ||
@PathParam("entityType") EntityType entityType, | ||
@PathParam("entityId") Integer entityId, | ||
@PathParam("roleId") Long roleId, | ||
AccessRequestDTO accessRequestDTO | ||
) throws Exception { | ||
|
||
permissionService.checkCommonEntityOwnership(entityType, entityId); | ||
Map<String, String> permissionTemplates = permissionService.getTemplatesForType(entityType, accessRequestDTO.getAccessType()); | ||
permissionService.removePermissionsFromRole(permissionTemplates, entityId, roleId); | ||
} | ||
} |
134 changes: 134 additions & 0 deletions
134
src/main/java/org/ohdsi/webapi/security/PermissionService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package org.ohdsi.webapi.security; | ||
|
||
import org.apache.shiro.authz.UnauthorizedException; | ||
import org.ohdsi.webapi.model.CommonEntity; | ||
import org.ohdsi.webapi.security.model.EntityPermissionSchema; | ||
import org.ohdsi.webapi.security.model.EntityPermissionSchemaResolver; | ||
import org.ohdsi.webapi.security.model.EntityType; | ||
import org.ohdsi.webapi.shiro.Entities.PermissionEntity; | ||
import org.ohdsi.webapi.shiro.Entities.PermissionRepository; | ||
import org.ohdsi.webapi.shiro.Entities.RoleEntity; | ||
import org.ohdsi.webapi.shiro.Entities.RolePermissionEntity; | ||
import org.ohdsi.webapi.shiro.Entities.RolePermissionRepository; | ||
import org.ohdsi.webapi.shiro.Entities.RoleRepository; | ||
import org.ohdsi.webapi.shiro.Entities.UserEntity; | ||
import org.ohdsi.webapi.shiro.PermissionManager; | ||
import org.springframework.aop.framework.Advised; | ||
import org.springframework.core.convert.ConversionService; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.repository.support.Repositories; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.context.WebApplicationContext; | ||
|
||
import javax.annotation.PostConstruct; | ||
import java.io.Serializable; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
@Service | ||
public class PermissionService { | ||
|
||
private final WebApplicationContext appContext; | ||
private final PermissionManager permissionManager; | ||
private final EntityPermissionSchemaResolver entityPermissionSchemaResolver; | ||
private final RoleRepository roleRepository; | ||
private final PermissionRepository permissionRepository; | ||
private final RolePermissionRepository rolePermissionRepository; | ||
private final ConversionService conversionService; | ||
|
||
private Repositories repositories; | ||
|
||
public PermissionService( | ||
WebApplicationContext appContext, | ||
PermissionManager permissionManager, | ||
EntityPermissionSchemaResolver entityPermissionSchemaResolver, | ||
RoleRepository roleRepository, | ||
PermissionRepository permissionRepository, | ||
RolePermissionRepository rolePermissionRepository, | ||
ConversionService conversionService | ||
) { | ||
|
||
this.appContext = appContext; | ||
this.permissionManager = permissionManager; | ||
this.entityPermissionSchemaResolver = entityPermissionSchemaResolver; | ||
this.roleRepository = roleRepository; | ||
this.permissionRepository = permissionRepository; | ||
this.rolePermissionRepository = rolePermissionRepository; | ||
this.conversionService = conversionService; | ||
} | ||
|
||
@PostConstruct | ||
private void postConstruct() { | ||
|
||
this.repositories = new Repositories(appContext); | ||
} | ||
|
||
public List<RoleEntity> suggestRoles(String roleSearch) { | ||
|
||
return roleRepository.findByNameIgnoreCaseContaining(roleSearch); | ||
} | ||
|
||
public Map<String, String> getTemplatesForType(EntityType entityType, AccessType accessType) { | ||
|
||
EntityPermissionSchema entityPermissionSchema = entityPermissionSchemaResolver.getForType(entityType); | ||
return getPermissionTemplates(entityPermissionSchema, accessType); | ||
} | ||
|
||
public void checkCommonEntityOwnership(EntityType entityType, Integer entityId) throws Exception { | ||
|
||
JpaRepository entityRepository = (JpaRepository) (((Advised) repositories.getRepositoryFor(entityType.getEntityClass())).getTargetSource().getTarget()); | ||
Class idClazz = Arrays.stream(entityType.getEntityClass().getMethods()) | ||
.filter(m -> m.getName().equals("getId")) | ||
.findFirst() | ||
.orElseThrow(() -> new RuntimeException("Cannot retrieve common entity")) | ||
.getReturnType(); | ||
CommonEntity entity = (CommonEntity) entityRepository.getOne((Serializable) conversionService.convert(entityId, idClazz)); | ||
|
||
if (!isCurrentUserOwnerOf(entity)) { | ||
throw new UnauthorizedException(); | ||
} | ||
} | ||
|
||
public Map<String, String> getPermissionTemplates(EntityPermissionSchema permissionSchema, AccessType accessType) { | ||
|
||
switch (accessType) { | ||
case WRITE: | ||
return permissionSchema.getWritePermissions(); | ||
default: | ||
throw new UnsupportedOperationException(); | ||
} | ||
} | ||
|
||
public List<RoleEntity> finaAllRolesHavingPermissions(List<String> permissions) { | ||
|
||
return roleRepository.finaAllRolesHavingPermissions(permissions, (long) permissions.size()); | ||
} | ||
|
||
public void removePermissionsFromRole(Map<String, String> permissionTemplates, Integer entityId, Long roleId) { | ||
|
||
RoleEntity role = roleRepository.findById(roleId); | ||
permissionTemplates.keySet() | ||
.forEach(pt -> { | ||
String permission = getPermission(pt, entityId); | ||
PermissionEntity permissionEntity = permissionRepository.findByValueIgnoreCase(permission); | ||
if (permissionEntity != null) { | ||
RolePermissionEntity rp = rolePermissionRepository.findByRoleAndPermission(role, permissionEntity); | ||
rolePermissionRepository.delete(rp.getId()); | ||
} | ||
}); | ||
} | ||
|
||
public String getPermission(String template, Object entityId) { | ||
|
||
return String.format(template, entityId); | ||
} | ||
|
||
private boolean isCurrentUserOwnerOf(CommonEntity entity) { | ||
|
||
UserEntity owner = entity.getCreatedBy(); | ||
String loggedInUsername = permissionManager.getSubjectName(); | ||
return Objects.equals(owner.getLogin(), loggedInUsername); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/org/ohdsi/webapi/security/converter/RoleEntityToRoleDTOConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.ohdsi.webapi.security.converter; | ||
|
||
import org.ohdsi.webapi.converter.BaseConversionServiceAwareConverter; | ||
import org.ohdsi.webapi.security.dto.RoleDTO; | ||
import org.ohdsi.webapi.shiro.Entities.RoleEntity; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class RoleEntityToRoleDTOConverter extends BaseConversionServiceAwareConverter<RoleEntity, RoleDTO> { | ||
|
||
@Override | ||
public RoleDTO convert(RoleEntity roleEntity) { | ||
|
||
return new RoleDTO(roleEntity.getId(), roleEntity.getName()); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/org/ohdsi/webapi/security/dto/AccessRequestDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package org.ohdsi.webapi.security.dto; | ||
|
||
import org.ohdsi.webapi.security.AccessType; | ||
|
||
public class AccessRequestDTO { | ||
|
||
private AccessType accessType; | ||
|
||
public AccessType getAccessType() { | ||
|
||
return accessType; | ||
} | ||
|
||
public void setAccessType(AccessType accessType) { | ||
|
||
this.accessType = accessType; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.ohdsi.webapi.security.dto; | ||
|
||
public class RoleDTO { | ||
|
||
private Long id; | ||
private String name; | ||
|
||
public RoleDTO() { | ||
|
||
} | ||
|
||
public RoleDTO(Long id, String name) { | ||
|
||
this.id = id; | ||
this.name = name; | ||
} | ||
|
||
public Long getId() { | ||
|
||
return id; | ||
} | ||
|
||
public void setId(Long id) { | ||
|
||
this.id = id; | ||
} | ||
|
||
public String getName() { | ||
|
||
return name; | ||
} | ||
|
||
public void setName(String name) { | ||
|
||
this.name = name; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/main/java/org/ohdsi/webapi/security/model/CohortCharacterizationPermissionSchema.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.ohdsi.webapi.security.model; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Component | ||
public class CohortCharacterizationPermissionSchema extends EntityPermissionSchema { | ||
|
||
private static Map<String, String> writePermissions = new HashMap<String, String>() {{ | ||
put("cohort-characterization:%s:put", "Update Cohort Characterization with ID = %s"); | ||
put("cohort-characterization:%s:delete", "Delete Cohort Characterization with ID = %s"); | ||
}}; | ||
|
||
public CohortCharacterizationPermissionSchema() { | ||
|
||
super(EntityType.COHORT_CHARACTERIZATION, new HashMap<>(), writePermissions); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/main/java/org/ohdsi/webapi/security/model/CohortDefinitionPermissionSchema.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.ohdsi.webapi.security.model; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Component | ||
public class CohortDefinitionPermissionSchema extends EntityPermissionSchema { | ||
|
||
private static Map<String, String> writePermissions = new HashMap<String, String>() {{ | ||
put("cohortdefinition:%s:put", "Update Cohort Definition with ID = %s"); | ||
put("cohortdefinition:%s:delete", "Delete Cohort Definition with ID = %s"); | ||
put("cohortdefinition:%s:check:post", "Fix Cohort Definition with ID = %s"); | ||
}}; | ||
|
||
public CohortDefinitionPermissionSchema() { | ||
|
||
super(EntityType.COHORT_DEFINITION, new HashMap<>(), writePermissions); | ||
} | ||
} |
Oops, something went wrong.