Skip to content

Commit

Permalink
Add data access token user role filter feature
Browse files Browse the repository at this point in the history
  • Loading branch information
kalletlak committed Aug 7, 2023
1 parent d035bb1 commit bb9a48f
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ public static String parseUrl(String url)
@Value("${download_group:}") // default is empty string
public void setDownloadGroup(String property) { downloadGroup = property; }

public static final String DEFAULT_DAT_METHOD = "none";

private static String tokenAccessUserRole;
@Value("${dat.filter_user_role:}") // default is empty string
public void setTokenAccessUserRole(String property) { tokenAccessUserRole = property; }

private static Logger LOG = LoggerFactory.getLogger(GlobalProperties.class);
private static ConfigPropertyResolver portalProperties = new ConfigPropertyResolver();
private static Properties mavenProperties = initializeProperties(MAVEN_PROPERTIES_FILE_NAME);
Expand Down Expand Up @@ -1296,4 +1302,16 @@ public static String getDownloadControl() {
}
}
}

public static String getDataAccessTokenMethod() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String dataAccessTokenMethod = getProperty("dat.method");

if (authentication != null &&
StringUtils.isNotEmpty(tokenAccessUserRole)) {
return authentication.getAuthorities().contains(new SimpleGrantedAuthority(tokenAccessUserRole)) ? dataAccessTokenMethod : DEFAULT_DAT_METHOD;
} else {
return dataAccessTokenMethod;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ The following properties must be present in portal.properties in order to allow
* **Permissible Values**: jwt, uuid, oauth2, none
* **Default Value**: none

**Property**: dat.filter\_user\_role (optional)

* **Description**: This property determines users access in token generation. If present, this role will be checked in user roles before generating a token.
* **Permissible Values**: A string value.
* **Default Value**: none

**Property**: dat.unauth\_users (optional, not used for dat.method = oauth2)

* **Description**: A list of users that should not be allowed to download a data access token.
Expand Down
3 changes: 2 additions & 1 deletion portal/src/main/webapp/config_service.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
String[] propNameArray = {
"app.version",
"app.name",
"dat.method",
"oncoprint.custom_driver_annotation.binary.menu_label",
"disabled_tabs",
"civic.url",
Expand Down Expand Up @@ -204,6 +203,8 @@
obj.put("sessionServiceEnabled", !StringUtils.isEmpty(GlobalProperties.getSessionServiceUrl()));
obj.put("skin_hide_download_controls", GlobalProperties.getDownloadControl());
obj.put("dat_method", GlobalProperties.getDataAccessTokenMethod());
out.println(obj.toJSONString());
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/portal.properties.EXAMPLE
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ dat.method=none
dat.ttl_seconds=2592000
dat.uuid.max_number_per_user=1
dat.jwt.secret_key=
dat.filter_user_role=

# OAuth2 token data access settings
#dat.oauth2.clientId=<client-id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.models.HttpMethod;
import org.apache.commons.lang3.StringUtils;
import org.cbioportal.model.DataAccessToken;
import org.cbioportal.service.DataAccessTokenService;
import org.cbioportal.service.exception.DataAccessTokenNoUserIdentityException;
Expand All @@ -32,6 +32,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
Expand All @@ -54,6 +55,10 @@ public class DataAccessTokenController {
@Value("${dat.unauth_users:anonymousUser}")
private String[] USERS_WHO_CANNOT_USE_TOKENS;

private static String userRoleToAccessToken;
@Value("${download_group:}") // default is empty string
public void setUserRoleToAccessToken(String property) { userRoleToAccessToken = property; }

@Autowired
private DataAccessTokenService tokenService;
private Set<String> usersWhoCannotUseTokenSet;
Expand Down Expand Up @@ -130,6 +135,10 @@ private String getAuthenticatedUser(Authentication authentication) {
if (usersWhoCannotUseTokenSet.contains(username)) {
throw new DataAccessTokenProhibitedUserException();
}
if(StringUtils.isNotEmpty(userRoleToAccessToken) &&
!authentication.getAuthorities().contains(new SimpleGrantedAuthority(userRoleToAccessToken))) {
throw new DataAccessTokenProhibitedUserException();
}
return username;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.cbioportal.model.DataAccessToken;
import org.cbioportal.service.DataAccessTokenService;
import org.cbioportal.service.exception.DataAccessTokenNoUserIdentityException;
import org.cbioportal.service.exception.DataAccessTokenProhibitedUserException;
import org.cbioportal.web.config.annotation.InternalApi;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -46,6 +48,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
Expand Down Expand Up @@ -76,6 +79,9 @@ public class OAuth2DataAccessTokenController {
@Value("${dat.oauth2.clientId}")
private String clientId;

@Value("${dat.filter_user_role:}") // default is empty string
private String userRoleToAccessToken;

@Autowired
private DataAccessTokenService tokenService;
private String authorizationUrl;
Expand All @@ -98,6 +104,8 @@ public void postConstruct() throws UnsupportedEncodingException {
public ResponseEntity<String> downloadDataAccessToken(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) throws IOException {

isUserAuthorizedToAccess(authentication);

// redirect to authentication endpoint
HttpHeaders headers = new HttpHeaders();
headers.add("Location", authorizationUrl);
Expand Down Expand Up @@ -156,4 +164,15 @@ public void revokeDataAccessToken(@ApiParam(required = true, value = "token") @P
throw new UnsupportedOperationException("this endpoint is does not apply to OAuth2 data access token method.");
}

private void isUserAuthorizedToAccess(Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) {
throw new DataAccessTokenNoUserIdentityException();
}

if(StringUtils.isNotEmpty(userRoleToAccessToken) &&
!authentication.getAuthorities().contains(new SimpleGrantedAuthority(userRoleToAccessToken))) {
throw new DataAccessTokenProhibitedUserException();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
Expand Down Expand Up @@ -190,6 +191,32 @@ public void createTokenValidUserTest() throws Exception {
.andReturn();
}

@Test
public void createTokenValidUserTestWithUserRole() throws Exception {
ReflectionTestUtils.setField(DataAccessTokenController.class, "userRoleToAccessToken", "PLACEHOLDER_ROLE");
Mockito.when(tokenService.createDataAccessToken(ArgumentMatchers.anyString())).thenReturn(MOCK_TOKEN_INFO);
HttpSession session = getSession(MOCK_USER, MOCK_PASSWORD);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/data-access-tokens")
.session((MockHttpSession) session)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andReturn();
}

@Test
public void createTokenUnauthorizedUserTestWithUserRole() throws Exception {
ReflectionTestUtils.setField(DataAccessTokenController.class, "userRoleToAccessToken", "TEST");
Mockito.when(tokenService.createDataAccessToken(ArgumentMatchers.anyString())).thenReturn(MOCK_TOKEN_INFO);
HttpSession session = getSession(MOCK_USER, MOCK_PASSWORD);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/data-access-tokens")
.session((MockHttpSession) session)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andReturn();
}

/* Tests mapping for DELETE /data-access-tokens
* Checks response status code is 200 success
* Checks that correct username argument is passed to service class
Expand Down

0 comments on commit bb9a48f

Please sign in to comment.