Skip to content

Commit

Permalink
RFC83: Add admin call to make virtual study available for all users o…
Browse files Browse the repository at this point in the history
…n their landing pages (#10829)

* Implement endpoints for public virtual studies

* Add possibility to specify cancer type id and pmi for virt. study during publishing

* Filter out forbidden study ids from virtual studies

* Do not allow set * user for user virtual study

To prevent missuse of the request to publish virtual study for everyone!

* Add integration tests for (un)publishing virtual study

* Assert fields fo published virtual studies

* Use recommended ways to inject dependencies in spring

* Add issue link to session service FIXMEs

* Fix sonar reported NPE bugs

* Remove unnecessary checks for null

* Remove obsolete TODO comment

* Throw AccessForbiddenException and use GlobalExceptionHadler instead

* Throw IllegalStateException is downstream server return unsuccessful result

* Remove raw use of ResponseEntity

* Throw bad request exception instead of returning ResponseEntity

* Do not filter out VS when user does not hava access to underlying study samples

* Fix integration tests

* Extract http calls to the session service to the handler

* Remove todo comment

User has to have * username witch is not likely

* Fix sonarcloud issues

* Deduplicate ensuring publisher api key is correct

* Remove usage of generic wildcard type

* Extract logic to update VS metadata fields into a method

* Document publishing virtual study feature

* Update docs/Create-And-Publish-Virtual-Study.md

Co-authored-by: pieterlukasse <[email protected]>

* Publish virtual study by modifying it instead of making copy

Also get rid of undocumented endpoint

* Improve name and docs of method to retrieve VS for user

* Assign VM after un-publshing to the owner

Make sure that we unpublish public virtual study. Fail otherwise.

---------

Co-authored-by: Charles Haynes <[email protected]>
Co-authored-by: pieterlukasse <[email protected]>
  • Loading branch information
3 people authored Jul 10, 2024
1 parent dc2c594 commit a4a5942
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 26 deletions.
61 changes: 61 additions & 0 deletions docs/Create-And-Publish-Virtual-Study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Create and Publish Virtual Study

A [Virtual Study](./user-guide/faq.md#what-is-a-virtual-study) defines a subset or a combination of samples from one or more studies in the system.

*Note*: To publish or un-publish a virtual study, your cBioPortal instance must be configured with `session.endpoint.publisher-api-key` in the `application.properties`.

## Create Virtual Study

To create a virtual study in cBioPortal, follow these steps:

1. Define the desired filters on the study or multiple studies summary page.
2. Click the button with the bookmark icon () in the top right corner of the screen.
3. Provide a title and description, then click the Save button. You will see a link that looks like:

```
https://<cbioportal_host>/study?id=<virtual_study_id>
```

4. Save the virtual study link or ID if you want to publish it.

If you are logged in, this virtual study will appear in the `My Virtual Studies` section on the landing page.
You can always find the ID of the virtual study from the URL of the page that opens after clicking on it.

## Publish Virtual Study

To publish a virtual study, you need to supply the publisher API key in the `X-PUBLISHER-API-KEY` header.

Here is a curl command to publish a virtual study:
```shell
curl \
-X POST \
-H 'X-PUBLISHER-API-KEY: <session.endpoint.publisher-api-key>' \
-v 'http://<cbioportal_host>/api/public_virtual_studies/<virtual_study_id>'
```
The published virtual study will appear under the `Public Virtual Studies` section (next to the `My Virtual Studies` section) on the landing page for all users of cBioPortal.

While publishing, you can specify the PubMed ID (`pmid`) and `typeOfCancerId` of the virtual study using the following command:
```shell
curl \
-X POST \
-H 'X-PUBLISHER-API-KEY: <session.endpoint.publisher-api-key>' \
-v 'http://<cbioportal_host>/api/public_virtual_studies/<virtual_study_id>?pmid=<pmid>&typeOfCancerId=<code>'
```

The type of cancer code should match the known types of cancers in the cBioPortal database.
If the type of cancer is specified, the published virtual study will appear under the respective cancer section on the landing page.
Specifying the `pmid` enables a link to the PubMed page of the study.

## Un-publish Virtual Study

To un-publish a virtual study, you need to supply the publisher API key in the `X-PUBLISHER-API-KEY` header.
After un-publishing, virtual study will no longer be displayed in the `Public Virtual Studies` section on the landing page.
However, it reappears in the `My Virtual Studies` section for the owner.

Here is the command to un-publish a virtual study:
```shell
curl \
-X DELETE \
-H 'X-PUBLISHER-API-KEY: <session.endpoint.publisher-api-key>' \
-v 'http://<cbioportal_host>/api/public_virtual_studies/<virtual_study_id>'
```
3 changes: 3 additions & 0 deletions docs/Data-Loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ To remove a study, the [cbioportalImporter script](/Data-Loading-Maintaining-Stu

## Example studies
Examples for the different types of data are available on the [File Formats](/File-Formats.md) page. The Provisional TCGA studies, downloadable from the [Data Sets section](https://www.cbioportal.org/datasets) are complete studies that can be used as reference when creating data files.

## Public Virtual Studies
If your new study data is a subset or a combination of existing studies in the system, consider using [Public Virtual Studies](./Create-And-Publish-Virtual-Study.md) instead of duplicating data.
10 changes: 9 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<selenium_chrome_driver.version>3.14.0</selenium_chrome_driver.version>
<selenium.version>4.17.0</selenium.version>
<sentry.version>7.1.0</sentry.version>
<apache_httpclient.version>5.2.1</apache_httpclient.version>


<!-- No sure what these are for -->
Expand Down Expand Up @@ -346,7 +347,14 @@
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>${sentry.version}</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${apache_httpclient.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import org.cbioportal.persistence.cachemaputil.CacheMapUtil;
import org.cbioportal.security.CancerStudyPermissionEvaluator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
Expand All @@ -17,28 +15,22 @@
// We are allowing users to enable method_authorization if optional_oauth2 is selected
@ConditionalOnExpression("{'oauth2','saml', 'saml_plus_basic'}.contains('${authenticate}') or ('optional_oauth2' eq '${authenticate}' and 'true' eq '${security.method_authorization_enabled}')")
public class MethodSecurityConfig {
@Value("${app.name:}")
private String appName;

@Value("${filter_groups_by_appname:true}")
private String doFilterGroupsByAppName;

@Value("${always_show_study_group:}")
private String alwaysShowCancerStudyGroup;

@Autowired
private CacheMapUtil cacheMapUtil;

@Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
public CancerStudyPermissionEvaluator cancerStudyPermissionEvaluator(
@Value("${app.name:}") String appName,
@Value("${filter_groups_by_appname:true}") String doFilterGroupsByAppName,
@Value("${always_show_study_group:}") String alwaysShowCancerStudyGroup,
CacheMapUtil cacheMapUtil
) {
return new CancerStudyPermissionEvaluator(appName, doFilterGroupsByAppName, alwaysShowCancerStudyGroup, cacheMapUtil);
}

@Bean
public MethodSecurityExpressionHandler createExpressionHandler(CancerStudyPermissionEvaluator cancerStudyPermissionEvaluator) {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(cancerStudyPermissionEvaluator());
expressionHandler.setPermissionEvaluator(cancerStudyPermissionEvaluator);
return expressionHandler;
}

@Bean
public CancerStudyPermissionEvaluator cancerStudyPermissionEvaluator() {
return new CancerStudyPermissionEvaluator(appName, doFilterGroupsByAppName, alwaysShowCancerStudyGroup, cacheMapUtil);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.cbioportal.service.exception;

public class AccessForbiddenException extends RuntimeException {
public AccessForbiddenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,31 @@


import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;

import com.mongodb.BasicDBObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.cbioportal.web.parameter.VirtualStudy;
import org.cbioportal.web.parameter.VirtualStudyData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class SessionServiceRequestHandler {

private static final Logger LOG = LoggerFactory.getLogger(SessionServiceRequestHandler.class);

@Value("${session.service.url:}")
private String sessionServiceURL;

Expand Down Expand Up @@ -62,4 +73,81 @@ public String getSessionDataJson(SessionType type, String id) throws Exception {
return responseEntity.getBody();
}

/**
* Gets virtual study by id
* @param id - id of the virtual study to read
* @return virtual study
*/
public VirtualStudy getVirtualStudyById(String id) {
ResponseEntity<VirtualStudy> responseEntity = new RestTemplate()
.exchange(sessionServiceURL + "/virtual_study/" + id,
HttpMethod.GET,
new HttpEntity<>(getHttpHeaders()),
VirtualStudy.class);
HttpStatusCode statusCode = responseEntity.getStatusCode();
VirtualStudy virtualStudy = responseEntity.getBody();
if (!statusCode.is2xxSuccessful() || virtualStudy == null) {
LOG.error("The downstream server replied with statusCode={} and body={}." +
" Replying with the same status code to the client.",
statusCode, virtualStudy);
throw new IllegalStateException("The downstream server response is not successful");
}
return responseEntity.getBody();
}

/**
* Get list of virtual studies accessible to user
* @param username - user for whom get list of virtual studies
* @return - list of virtual studies
*/
public List<VirtualStudy> getVirtualStudiesAccessibleToUser(String username) {
BasicDBObject basicDBObject = new BasicDBObject();
basicDBObject.put("data.users", username);
ResponseEntity<List<VirtualStudy>> responseEntity = new RestTemplate().exchange(
sessionServiceURL + "/virtual_study/query/fetch",
HttpMethod.POST,
new HttpEntity<>(basicDBObject.toString(), getHttpHeaders()),
new ParameterizedTypeReference<>() {
});

return responseEntity.getBody();
}

/**
* Creates a virtual study out of virtual study definition (aka virtualStudyData)
* @param virtualStudyData - definition of virtual study
* @return virtual study object with id and the virtualStudyData
*/
public VirtualStudy createVirtualStudy(VirtualStudyData virtualStudyData) {
ResponseEntity<VirtualStudy> responseEntity = new RestTemplate().exchange(
sessionServiceURL + "/virtual_study",
HttpMethod.POST,
new HttpEntity<>(virtualStudyData, getHttpHeaders()),
new ParameterizedTypeReference<>() {
});

return responseEntity.getBody();
}


/**
* Soft delete of the virtual study by de-associating all assigned users.
* @param id - id of virtual study to soft delete
*/
public void softRemoveVirtualStudy(String id) {
VirtualStudy virtualStudy = getVirtualStudyById(id);
VirtualStudyData data = virtualStudy.getData();
data.setUsers(Collections.emptySet());
updateVirtualStudy(virtualStudy);
}

/**
* Updates virtual study
* @param virtualStudy - virtual study to update
*/
public void updateVirtualStudy(VirtualStudy virtualStudy) {
new RestTemplate()
.put(sessionServiceURL + "/virtual_study/" + virtualStudy.getId(),
new HttpEntity<>(virtualStudy.getData(), getHttpHeaders()));
}
}
Loading

0 comments on commit a4a5942

Please sign in to comment.