-
Notifications
You must be signed in to change notification settings - Fork 7
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
[PF-1681] Add discoverer policy when creating SAM workspace resource #755
Changes from all commits
12d1ea7
571d23f
e6a3a1b
8268fa9
8de451d
f64dab8
faac7d0
99f9a65
285fc2b
17d64b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -300,8 +300,14 @@ public Map<UUID, WsmIamRole> listWorkspaceIdsAndHighestRoles(AuthenticatedUserRe | |
.filter(Objects::nonNull) | ||
.collect(Collectors.toList()); | ||
Optional<WsmIamRole> highestRole = WsmIamRole.getHighestRole(workspaceId, roles); | ||
// Skip workspaces with no roles. (That means there's a role this WSM doesn't know about.) | ||
// Skip workspaces with no roles. (That means there's a role this WSM doesn't know | ||
// about.) | ||
if (highestRole.isPresent()) { | ||
// TODO(PF-1875): Support requesting discoverer workspaces in ListWorkspaces. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just filter in ListWorkspaces, so you don't have to remember to come back to this code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the next PR, I updated both here and ListWorkspaces. |
||
// For now, don't return discoverer workspaces. | ||
if (highestRole.get().equals(WsmIamRole.DISCOVERER)) { | ||
continue; | ||
} | ||
workspacesAndRoles.put(workspaceId, highestRole.get()); | ||
} | ||
} catch (IllegalArgumentException e) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package bio.terra.workspace.service.workspace.flight; | ||
|
||
import static bio.terra.workspace.service.workspace.CloudSyncRoleMapping.CUSTOM_GCP_PROJECT_IAM_ROLES; | ||
import static bio.terra.workspace.service.workspace.flight.WorkspaceFlightMapKeys.GCP_PROJECT_ID; | ||
|
||
import bio.terra.cloudres.google.cloudresourcemanager.CloudResourceManagerCow; | ||
|
@@ -71,7 +72,11 @@ public StepResult doStep(FlightContext flightContext) | |
newBindings.addAll(currentPolicy.getBindings()); | ||
// Add appropriate project-level roles for each WSM IAM role. | ||
workspaceRoleGroupsMap.forEach( | ||
(role, email) -> newBindings.add(bindingForRole(role, email, gcpProjectId))); | ||
(wsmRole, email) -> { | ||
if (CUSTOM_GCP_PROJECT_IAM_ROLES.containsKey(wsmRole)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: now that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
newBindings.add(bindingForRole(wsmRole, email, gcpProjectId)); | ||
} | ||
}); | ||
|
||
Policy newPolicy = | ||
new Policy() | ||
|
@@ -102,7 +107,7 @@ private String toMemberIdentifier(String samEmail) { | |
* @param gcpProjectId The ID of the project the custom role is defined in. | ||
*/ | ||
private Binding bindingForRole(WsmIamRole role, String email, String gcpProjectId) { | ||
CustomGcpIamRole customRole = CloudSyncRoleMapping.CUSTOM_GCP_PROJECT_IAM_ROLES.get(role); | ||
CustomGcpIamRole customRole = CUSTOM_GCP_PROJECT_IAM_ROLES.get(role); | ||
return new Binding() | ||
.setRole(customRole.getFullyQualifiedRoleName(gcpProjectId)) | ||
.setMembers(Collections.singletonList(toMemberIdentifier(email))); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package bio.terra.workspace.app.configuration.external.controller; | ||
|
||
import static bio.terra.workspace.common.utils.MockMvcUtils.GRANT_ROLE_PATH_FORMAT; | ||
import static bio.terra.workspace.common.utils.MockMvcUtils.WORKSPACES_V1_BY_UUID_PATH_FORMAT; | ||
import static bio.terra.workspace.common.utils.MockMvcUtils.WORKSPACES_V1_PATH; | ||
import static bio.terra.workspace.common.utils.MockMvcUtils.addAuth; | ||
import static bio.terra.workspace.common.utils.MockMvcUtils.addJsonContentType; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
import bio.terra.workspace.common.BaseConnectedTest; | ||
import bio.terra.workspace.common.fixtures.WorkspaceFixtures; | ||
import bio.terra.workspace.connected.UserAccessUtils; | ||
import bio.terra.workspace.generated.model.ApiCreatedWorkspace; | ||
import bio.terra.workspace.generated.model.ApiGrantRoleRequestBody; | ||
import bio.terra.workspace.generated.model.ApiWorkspaceDescription; | ||
import bio.terra.workspace.generated.model.ApiWorkspaceDescriptionList; | ||
import bio.terra.workspace.service.iam.AuthenticatedUserRequest; | ||
import bio.terra.workspace.service.iam.model.WsmIamRole; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import java.util.List; | ||
import java.util.UUID; | ||
import org.apache.http.HttpStatus; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
/** | ||
* Connected tests for WorkspaceApiController. | ||
* | ||
* <p>In general, we would like to move towards testing new endpoints via controller instead of | ||
* calling services directly like we have in the past. Although this test duplicates coverage | ||
* currently in WorkspaceServiceTest, it's intended as a proof-of-concept for future mockMvc-based | ||
* tests. | ||
* | ||
* <p>Use this instead of WorkspaceApiControllerTest, if you want to use real | ||
* bio.terra.workspace.service.iam.SamService. | ||
*/ | ||
@AutoConfigureMockMvc | ||
public class WorkspaceApiControllerConnectedTest extends BaseConnectedTest { | ||
|
||
@Autowired private MockMvc mockMvc; | ||
@Autowired private ObjectMapper objectMapper; | ||
@Autowired private UserAccessUtils userAccessUtils; | ||
|
||
private ApiCreatedWorkspace workspace; | ||
|
||
@BeforeEach | ||
public void setup() throws Exception { | ||
workspace = createWorkspace(); | ||
} | ||
|
||
/** Clean up workspaces from Broad dev SAM. */ | ||
@AfterEach | ||
public void cleanup() throws Exception { | ||
deleteWorkspace(workspace.getId()); | ||
} | ||
|
||
@Test | ||
public void getWorkspace_doesNotReturnWorkspaceWithOnlyDiscovererRole() throws Exception { | ||
grantRole(workspace.getId(), WsmIamRole.DISCOVERER, userAccessUtils.getSecondUserEmail()); | ||
|
||
getWorkspaceDescriptionExpectingError( | ||
userAccessUtils.secondUserAuthRequest(), workspace.getId(), HttpStatus.SC_FORBIDDEN); | ||
} | ||
|
||
@Test | ||
public void listWorkspaces_doesNotReturnWorkspaceWithOnlyDiscovererRole() throws Exception { | ||
grantRole(workspace.getId(), WsmIamRole.DISCOVERER, userAccessUtils.getSecondUserEmail()); | ||
|
||
List<ApiWorkspaceDescription> listedWorkspaces = | ||
listWorkspaces(userAccessUtils.secondUserAuthRequest()); | ||
assertTrue(listedWorkspaces.isEmpty()); | ||
} | ||
|
||
private ApiCreatedWorkspace createWorkspace() throws Exception { | ||
var createRequest = WorkspaceFixtures.createWorkspaceRequestBody(); | ||
String serializedResponse = | ||
mockMvc | ||
.perform( | ||
addJsonContentType( | ||
addAuth( | ||
post(WORKSPACES_V1_PATH) | ||
.content(objectMapper.writeValueAsString(createRequest)), | ||
userAccessUtils.defaultUserAuthRequest()))) | ||
.andExpect(status().is(HttpStatus.SC_OK)) | ||
.andReturn() | ||
.getResponse() | ||
.getContentAsString(); | ||
return objectMapper.readValue(serializedResponse, ApiCreatedWorkspace.class); | ||
} | ||
|
||
private void getWorkspaceDescriptionExpectingError( | ||
AuthenticatedUserRequest userRequest, UUID id, int statusCode) throws Exception { | ||
mockMvc | ||
.perform(addAuth(get(String.format(WORKSPACES_V1_BY_UUID_PATH_FORMAT, id)), userRequest)) | ||
.andExpect(status().is(statusCode)); | ||
} | ||
|
||
private List<ApiWorkspaceDescription> listWorkspaces(AuthenticatedUserRequest request) | ||
throws Exception { | ||
String serializedResponse = | ||
mockMvc | ||
.perform(addAuth(get(WORKSPACES_V1_PATH), request)) | ||
.andExpect(status().is(HttpStatus.SC_OK)) | ||
.andReturn() | ||
.getResponse() | ||
.getContentAsString(); | ||
return objectMapper | ||
.readValue(serializedResponse, ApiWorkspaceDescriptionList.class) | ||
.getWorkspaces(); | ||
} | ||
|
||
private void grantRole(UUID workspaceId, WsmIamRole role, String memberEmail) throws Exception { | ||
var requestBody = new ApiGrantRoleRequestBody().memberEmail(memberEmail); | ||
mockMvc | ||
.perform( | ||
addJsonContentType( | ||
addAuth( | ||
post(String.format(GRANT_ROLE_PATH_FORMAT, workspaceId, role.name())) | ||
.content(objectMapper.writeValueAsString(requestBody)), | ||
userAccessUtils.defaultUserAuthRequest()))) | ||
.andExpect(status().is(HttpStatus.SC_NO_CONTENT)) | ||
.andReturn() | ||
.getResponse() | ||
.getContentAsString(); | ||
} | ||
|
||
private void deleteWorkspace(UUID workspaceId) throws Exception { | ||
mockMvc | ||
.perform( | ||
addAuth( | ||
delete(String.format(WORKSPACES_V1_BY_UUID_PATH_FORMAT, workspaceId)), | ||
userAccessUtils.defaultUserAuthRequest())) | ||
.andExpect(status().is(HttpStatus.SC_NO_CONTENT)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is incorrect. Please double check my understanding @zloery
Janitor just deletes projects, it does not do anything to clean up Sam, so we should never skip in-test cleanup.
Connected tests also use RBS and Janitor. It doesn't have anything to do with WSM being ephemeral, since Janitor is just deleting projects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use Janitor in two ways:
tools
RBS instance registers every project it hands out with Janitor to be cleaned up after some time. Since all our connected tests talk to this instance, the GCP projects get cleaned up eventuallyWe can't use the second pattern for WSM connected tests because the instance is ephemeral (so there's nothing for Janitor to call later), but we can and do use the first pattern to make sure the GCP projects get cleaned up. the Sam workspaces will still be around if the test doesn't clean them up, though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated:
https://github.com/DataBiosphere/terra-workspace-manager/blob/17d64b6d6cf4b891977230672b6f4cc9cd592105/README.md#cleaning-up-workspaces-in-tests
This has @zloery 's stamp of approval.