Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tools module functionality #2389

Merged
merged 21 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9cfb5dd
ATL-9: Tools Module
jcnamendiOdysseus Nov 29, 2023
6af3313
date time update
jcnamendiOdysseus Jan 4, 2024
9a5c4b5
Update Tools module
jcnamendiOdysseus Jan 8, 2024
4f8044b
Address null exception in case of disabled security
jcnamendiOdysseus Jan 31, 2024
e2499b2
- adding permissions to sec_permission, sec_role_permission (migratio…
alex-odysseus Mar 6, 2024
024384b
Remove validation from the Tool class.
jcnamendiOdysseus Mar 7, 2024
36555aa
Remove validation from the Tool class.
jcnamendiOdysseus Mar 7, 2024
3bb1bfe
Add tool permission
jcnamendiOdysseus Apr 3, 2024
498b4c9
add tools messages i18n
jcnamendiOdysseus Apr 11, 2024
132c1c7
Fixed deleting a record would delete the role
jcnamendiOdysseus Apr 18, 2024
389fac5
Remove unused imports
jcnamendiOdysseus Apr 18, 2024
a49ceab
Check if the delete permission for tools is present to add it back.
jcnamendiOdysseus May 6, 2024
88b51dd
Oracle and Microsoft SQL migrations should be no longer supported
alex-odysseus Aug 30, 2024
2cfa1a1
Removing unused translations
alex-odysseus Aug 30, 2024
5acc126
Fixed issue where non-administrator tool editor was unable to save en…
oleg-odysseus Oct 25, 2024
a6d5274
Allowed reading non-enabled tools by a user having 'tool:post && tool…
oleg-odysseus Nov 1, 2024
1e36b5c
Combining migration scripts into one, removing redundant classes, min…
alex-odysseus Dec 17, 2024
4fd6380
ToolRepository method name changes after property renaming in Tool
alex-odysseus Dec 17, 2024
fdf2d79
Aligning with the permissions notation from the migration script
alex-odysseus Jan 8, 2025
ad014ab
Removing malformed entries from the permission schema correcting a mi…
alex-odysseus Feb 17, 2025
616d369
Merge remote-tracking branch 'remotes/origin/master' into tools-module
chrisknoll Feb 25, 2025
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
2 changes: 2 additions & 0 deletions src/main/java/org/ohdsi/webapi/security/model/EntityType.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.ohdsi.webapi.reusable.domain.Reusable;
import org.ohdsi.webapi.source.Source;
import org.ohdsi.webapi.tag.domain.Tag;
import org.ohdsi.webapi.tool.Tool;

public enum EntityType {
COHORT_DEFINITION(CohortDefinition.class),
Expand All @@ -26,6 +27,7 @@ public enum EntityType {
PREDICTION(PredictionAnalysis.class),
COHORT_SAMPLE(CohortSample.class),
TAG(Tag.class),
TOOL(Tool.class),
REUSABLE(Reusable.class);

private final Class<? extends CommonEntity> entityClass;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.ohdsi.webapi.security.model;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class ToolPermissionSchema extends EntityPermissionSchema {
private static Map<String, String> writePermissions = new HashMap<String, String>() {{
put("tool:%s:delete", "Delete Tool with id = %s");
put("tool:put", "Update a Tool");
put("tool:post", "Create a Tool");
Copy link
Collaborator

@chrisknoll chrisknoll Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work: what will happen is that when the Tool asset is deleted, all these permissions will be deleted and everyone will lose the ability to create and update a tool.

This is an example of the complication of the permission implementation we have in webapi that we'll want to resolve in 3.x or 4.x.

For details on how the non-entity specific permissions caused issues, you can refer to this issue: #2412.

Essentially, the only permissions that go into PermissionSchema are those with entity-id context. The general ones can't be there because deleting one asset will delete the entire (non-asset specific) set of permissions from everyone.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alex-odysseus , please provide update to this issue.

}};
private static Map<String, String> readPermissions = new HashMap<String, String>() {
{
put("tool:%s:get", "View Tool with id = %s");
}
};

public ToolPermissionSchema() {
super(EntityType.TOOL, readPermissions, writePermissions);
}

}
93 changes: 93 additions & 0 deletions src/main/java/org/ohdsi/webapi/tool/Tool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.ohdsi.webapi.tool;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.ohdsi.webapi.model.CommonEntity;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Column;
import java.util.Objects;

@Entity
@Table(name = "tool")
public class Tool extends CommonEntity<Integer> {
@Id
@GenericGenerator(
name = "tool_generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "tool_seq"),
@Parameter(name = "increment_size", value = "1")
}
)
@GeneratedValue(generator = "tool_generator")
private Integer id;

@Column(name = "name")
private String name;

@Column(name = "url")
private String url;

@Column(name = "description")
private String description;
@Column(name = "is_enabled")
private Boolean enabled;

@Override
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Boolean getEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tool tool = (Tool) o;
return Objects.equals(name, tool.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
62 changes: 62 additions & 0 deletions src/main/java/org/ohdsi/webapi/tool/ToolController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.ohdsi.webapi.tool;

import org.ohdsi.webapi.tool.dto.ToolDTO;
import org.springframework.stereotype.Controller;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Controller
@Path("/tool")
public class ToolController {
private final ToolServiceImpl service;

public ToolController(ToolServiceImpl service) {
this.service = service;
}

@GET
@Path("")
@Produces(MediaType.APPLICATION_JSON)
public List<ToolDTO> getTools() {
return service.getTools();
}

@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public ToolDTO getToolById(@PathParam("id") Integer id) {
return service.getById(id);
}

@POST
@Path("")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ToolDTO createTool(ToolDTO dto) {
return service.saveTool(dto);
}

@DELETE
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public void delete(@PathParam("id") Integer id) {
service.delete(id);
}

@PUT
@Path("")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public ToolDTO updateTool(ToolDTO toolDTO) {
return service.saveTool(toolDTO);
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/ohdsi/webapi/tool/ToolRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.ohdsi.webapi.tool;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ToolRepository extends JpaRepository<Tool, Integer> {
List<Tool> findAllByEnabled(boolean enabled);
}
13 changes: 13 additions & 0 deletions src/main/java/org/ohdsi/webapi/tool/ToolService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ohdsi.webapi.tool;

import org.ohdsi.webapi.tool.dto.ToolDTO;

import java.util.List;

public interface ToolService {
List<ToolDTO> getTools();
ToolDTO saveTool(ToolDTO toolDTO);
ToolDTO getById(Integer id);

void delete(Integer id);
}
120 changes: 120 additions & 0 deletions src/main/java/org/ohdsi/webapi/tool/ToolServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.ohdsi.webapi.tool;

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.shiro.SecurityUtils;
import org.ohdsi.webapi.service.AbstractDaoService;
import org.ohdsi.webapi.shiro.Entities.UserEntity;
import org.ohdsi.webapi.tool.dto.ToolDTO;
import org.springframework.stereotype.Service;

@Service
public class ToolServiceImpl extends AbstractDaoService implements ToolService {
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

private final ToolRepository toolRepository;

public ToolServiceImpl(ToolRepository toolRepository) {
this.toolRepository = toolRepository;
}

@Override
public List<ToolDTO> getTools() {
List<Tool> tools = (isAdmin() || canManageTools()) ? toolRepository.findAll() : toolRepository.findAllByEnabled(true);
return tools.stream()
.map(this::toDTO).collect(Collectors.toList());
}

@Override
public ToolDTO saveTool(ToolDTO toolDTO) {
Tool tool = saveToolFromDTO(toolDTO, getCurrentUser());
return toDTO(toolRepository.saveAndFlush(tool));
}

private Tool saveToolFromDTO(ToolDTO toolDTO, UserEntity currentUser) {
Tool tool = toEntity(toolDTO);
if (toolDTO.getId() == null) {
tool.setCreatedBy(currentUser);
}
tool.setModifiedBy(currentUser);
return tool;
}

@Override
public ToolDTO getById(Integer id) {
return toDTO(toolRepository.findOne(id));
}

@Override
public void delete(Integer id) {
toolRepository.delete(id);
}

private boolean canManageTools() {
return Stream.of("tool:put", "tool:post", "tool:*:delete")
.allMatch(permission -> SecurityUtils.getSubject().isPermitted(permission));
}

Tool toEntity(ToolDTO toolDTO) {
boolean isNewTool = toolDTO.getId() == null;
Tool tool = isNewTool ? new Tool() : toolRepository.findOne(toolDTO.getId());
Instant currentInstant = Instant.now();
if (isNewTool) {
setCreationDetails(tool, currentInstant);
} else {
setModificationDetails(tool, currentInstant);
}
updateToolFromDTO(tool, toolDTO);
return tool;
}

private void setCreationDetails(Tool tool, Instant currentInstant) {
tool.setCreatedDate(Date.from(currentInstant));
tool.setCreatedBy(getCurrentUser());
}

private void setModificationDetails(Tool tool, Instant currentInstant) {
tool.setModifiedDate(Date.from(currentInstant));
tool.setModifiedBy(getCurrentUser());
}

private void updateToolFromDTO(Tool tool, ToolDTO toolDTO) {
Optional.ofNullable(toolDTO.getName()).ifPresent(tool::setName);
Optional.ofNullable(toolDTO.getUrl()).ifPresent(tool::setUrl);
Optional.ofNullable(toolDTO.getDescription()).ifPresent(tool::setDescription);
Optional.ofNullable(toolDTO.getEnabled()).ifPresent(tool::setEnabled);
}

ToolDTO toDTO(Tool tool) {
return Optional.ofNullable(tool)
.map(t -> {
ToolDTO toolDTO = new ToolDTO();
toolDTO.setId(t.getId());
toolDTO.setName(t.getName());
toolDTO.setUrl(t.getUrl());
toolDTO.setDescription(t.getDescription());
Optional.ofNullable(tool.getCreatedBy())
.map(UserEntity::getId)
.map(userRepository::findOne)
.map(UserEntity::getName)
.ifPresent(toolDTO::setCreatedByName);
Optional.ofNullable(tool.getModifiedBy())
.map(UserEntity::getId)
.map(userRepository::findOne)
.map(UserEntity::getName)
.ifPresent(toolDTO::setModifiedByName);
toolDTO.setCreatedDate(t.getCreatedDate() != null ? new SimpleDateFormat(DATE_TIME_FORMAT).format(t.getCreatedDate()) : null);
toolDTO.setModifiedDate(t.getModifiedDate() != null ? new SimpleDateFormat(DATE_TIME_FORMAT).format(t.getModifiedDate()) : null);
toolDTO.setEnabled(t.getEnabled());
return toolDTO;
})
.orElse(null);
}

}
Loading
Loading