use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.
the class ControlledResourceServiceTest method createAiNotebookInstanceNoWriterRoleThrowsBadRequest.
@Test
@DisabledIfEnvironmentVariable(named = "TEST_ENV", matches = BUFFER_SERVICE_DISABLED_ENVS_REG_EX)
void createAiNotebookInstanceNoWriterRoleThrowsBadRequest() throws Exception {
String instanceId = "create-ai-notebook-instance-shared";
ApiGcpAiNotebookInstanceCreationParameters creationParameters = ControlledResourceFixtures.defaultNotebookCreationParameters().instanceId(instanceId).location(DEFAULT_NOTEBOOK_LOCATION);
ControlledAiNotebookInstanceResource resource = makeNotebookTestResource(workspace.getWorkspaceId(), instanceId, instanceId);
// Shared notebooks not yet implemented.
// Private IAM roles must include writer role.
ControlledResourceIamRole notWriter = ControlledResourceIamRole.READER;
BadRequestException noWriterException = assertThrows(BadRequestException.class, () -> controlledResourceService.createAiNotebookInstance(resource, creationParameters, notWriter, new ApiJobControl().id(UUID.randomUUID().toString()), "fakeResultPath", user.getAuthenticatedRequest()));
assertEquals("A private, controlled AI Notebook instance must have the writer or editor role or else it is not useful.", noWriterException.getMessage());
}
use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.
the class CopyGcsBucketDefinitionStep method doStep.
@Override
public StepResult doStep(FlightContext flightContext) throws InterruptedException, RetryException {
final FlightMap inputParameters = flightContext.getInputParameters();
final FlightMap workingMap = flightContext.getWorkingMap();
final CloningInstructions cloningInstructions = Optional.ofNullable(inputParameters.get(ControlledResourceKeys.CLONING_INSTRUCTIONS, CloningInstructions.class)).orElse(sourceBucket.getCloningInstructions());
// future steps need the resolved cloning instructions
workingMap.put(ControlledResourceKeys.CLONING_INSTRUCTIONS, cloningInstructions);
if (CloningInstructions.COPY_NOTHING.equals(cloningInstructions)) {
final ApiClonedControlledGcpGcsBucket noOpResult = new ApiClonedControlledGcpGcsBucket().effectiveCloningInstructions(cloningInstructions.toApiModel()).bucket(null).sourceWorkspaceId(sourceBucket.getWorkspaceId()).sourceResourceId(sourceBucket.getResourceId());
FlightUtils.setResponse(flightContext, noOpResult, HttpStatus.OK);
return StepResult.getStepResultSuccess();
}
// todo: handle COPY_REFERENCE PF-811, PF-812
final String resourceName = FlightUtils.getInputParameterOrWorkingValue(flightContext, ResourceKeys.RESOURCE_NAME, ResourceKeys.PREVIOUS_RESOURCE_NAME, String.class);
final String description = FlightUtils.getInputParameterOrWorkingValue(flightContext, ResourceKeys.RESOURCE_DESCRIPTION, ResourceKeys.PREVIOUS_RESOURCE_DESCRIPTION, String.class);
final String bucketName = Optional.ofNullable(inputParameters.get(ControlledResourceKeys.DESTINATION_BUCKET_NAME, String.class)).orElseGet(this::randomBucketName);
final PrivateResourceState privateResourceState = sourceBucket.getAccessScope() == AccessScopeType.ACCESS_SCOPE_PRIVATE ? PrivateResourceState.INITIALIZING : PrivateResourceState.NOT_APPLICABLE;
// Store effective bucket name for destination
workingMap.put(ControlledResourceKeys.DESTINATION_BUCKET_NAME, bucketName);
final UUID destinationWorkspaceId = inputParameters.get(ControlledResourceKeys.DESTINATION_WORKSPACE_ID, UUID.class);
// bucket resource for create flight
ControlledResourceFields commonFields = ControlledResourceFields.builder().workspaceId(destinationWorkspaceId).resourceId(// random ID for new resource
UUID.randomUUID()).name(resourceName).description(description).cloningInstructions(sourceBucket.getCloningInstructions()).assignedUser(sourceBucket.getAssignedUser().orElse(null)).accessScope(sourceBucket.getAccessScope()).managedBy(sourceBucket.getManagedBy()).applicationId(sourceBucket.getApplicationId()).privateResourceState(privateResourceState).build();
ControlledGcsBucketResource destinationBucketResource = ControlledGcsBucketResource.builder().bucketName(bucketName).common(commonFields).build();
final ApiGcpGcsBucketCreationParameters destinationCreationParameters = getDestinationCreationParameters(inputParameters, workingMap);
final ControlledResourceIamRole iamRole = IamRoleUtils.getIamRoleForAccessScope(sourceBucket.getAccessScope());
// Launch a CreateControlledResourcesFlight to make the destination bucket
final ControlledGcsBucketResource clonedBucket = controlledResourceService.createControlledResourceSync(destinationBucketResource, iamRole, userRequest, destinationCreationParameters).castByEnum(WsmResourceType.CONTROLLED_GCP_GCS_BUCKET);
workingMap.put(ControlledResourceKeys.CLONED_RESOURCE_DEFINITION, clonedBucket);
final ApiCreatedControlledGcpGcsBucket apiCreatedBucket = new ApiCreatedControlledGcpGcsBucket().gcpBucket(clonedBucket.toApiResource()).resourceId(destinationBucketResource.getResourceId());
final ApiClonedControlledGcpGcsBucket apiBucketResult = new ApiClonedControlledGcpGcsBucket().effectiveCloningInstructions(cloningInstructions.toApiModel()).bucket(apiCreatedBucket).sourceWorkspaceId(sourceBucket.getWorkspaceId()).sourceResourceId(sourceBucket.getResourceId());
workingMap.put(ControlledResourceKeys.CLONE_DEFINITION_RESULT, apiBucketResult);
if (cloningInstructions.equals(CloningInstructions.COPY_DEFINITION)) {
FlightUtils.setResponse(flightContext, apiBucketResult, HttpStatus.OK);
}
return StepResult.getStepResultSuccess();
}
use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.
the class ClaimUserPrivateResourcesStep method doStep.
@Override
public StepResult doStep(FlightContext context) throws InterruptedException, RetryException {
FlightMap workingMap = context.getWorkingMap();
boolean userStillInWorkspace = workingMap.get(ControlledResourceKeys.REMOVED_USER_IS_WORKSPACE_MEMBER, Boolean.class);
// to private resources.
if (userStillInWorkspace) {
return StepResult.getStepResultSuccess();
}
// Read the list of resources this user owns from WSM's DB which are not being cleaned up by
// other flights and indicate this flight is cleaning them up.
List<ControlledResource> userResources = resourceDao.claimCleanupForWorkspacePrivateResources(workspaceId, userEmail, context.getFlightId());
// For each private resource, query Sam to find the roles the user has.
List<ResourceRolePair> resourceRolesToRemove = new ArrayList<>();
for (ControlledResource resource : userResources) {
List<ControlledResourceIamRole> userRoles = samService.getUserRolesOnPrivateResource(resource, userEmail, userRequest);
for (ControlledResourceIamRole role : userRoles) {
resourceRolesToRemove.add(new ResourceRolePair(resource, role));
}
}
workingMap.put(ControlledResourceKeys.RESOURCE_ROLES_TO_REMOVE, resourceRolesToRemove);
return StepResult.getStepResultSuccess();
}
use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.
the class SamService method getUserRolesOnPrivateResource.
/**
* Return the list of roles a user has directly on a private, user-managed controlled resource.
* This will not return roles that a user holds via group membership.
*
* <p>This call to Sam is made as the WSM SA, as users do not have permission to directly modify
* IAM on resources. This method still requires user credentials to validate as a safeguard, but
* they are not used in the role removal call.
*
* @param resource The resource to fetch roles on
* @param userEmail Email identifier of the user whose role is being removed.
* @param userRequest User credentials. These are not used for the call to Sam, but must belong to
* a workspace owner to ensure the WSM SA is being used on a user's behalf correctly.
*/
public List<ControlledResourceIamRole> getUserRolesOnPrivateResource(ControlledResource resource, String userEmail, AuthenticatedUserRequest userRequest) throws InterruptedException {
// Validate that the provided user credentials can modify the owners of the resource's
// workspace.
// Although the Sam call to revoke a resource role must use WSM SA credentials instead, this
// is a safeguard against accidentally invoking these credentials for unauthorized users.
checkAuthz(userRequest, SamConstants.SamResource.WORKSPACE, resource.getWorkspaceId().toString(), samActionToModifyRole(WsmIamRole.OWNER));
try {
ResourcesApi wsmSaResourceApi = samResourcesApi(getWsmServiceAccountToken());
List<AccessPolicyResponseEntryV2> policyList = wsmSaResourceApi.listResourcePoliciesV2(resource.getCategory().getSamResourceName(), resource.getResourceId().toString());
return policyList.stream().filter(policyEntry -> policyEntry.getPolicy().getMemberEmails().contains(userEmail)).map(AccessPolicyResponseEntryV2::getPolicyName).map(ControlledResourceIamRole::fromSamRole).collect(Collectors.toList());
} catch (ApiException apiException) {
throw SamExceptionFactory.create("Sam error removing resource role in Sam", apiException);
}
}
use of bio.terra.workspace.service.iam.model.ControlledResourceIamRole in project terra-workspace-manager by DataBiosphere.
the class ControllerBase method computePrivateUserRole.
/**
* Validate and provide defaulting for the private resource user. The property is never required.
* The only time it is allowed is for application-private resources. If it is populated, we
* validate the user email and the specified IAM roles.
*
* <p>user-private resources are always assigned to the caller. You can't create a user-private
* resource and assign it to someone else. Because we can read the caller's email from the
* AuthenticatedUserRequest, we don't need to supply assignedUser in the request body.
*
* <p>application-private resources can be assigned to users other than the caller. For example,
* Leo could call WSM to create a VM (using the Leo SA's auth token) and request it be assigned to
* user X, not to the Leo SA.
*
* @param commonFields common fields from a controlled resource create request
* @param userRequest authenticate user
* @return PrivateUserRole holding the user email and the role list
*/
public PrivateUserRole computePrivateUserRole(UUID workspaceId, ApiControlledResourceCommonFields commonFields, AuthenticatedUserRequest userRequest) {
AccessScopeType accessScope = AccessScopeType.fromApi(commonFields.getAccessScope());
ManagedByType managedBy = ManagedByType.fromApi(commonFields.getManagedBy());
ApiPrivateResourceUser inputUser = commonFields.getPrivateResourceUser();
// Shared access has no private user role
if (accessScope == AccessScopeType.ACCESS_SCOPE_SHARED) {
validateNoInputUser(inputUser);
return new PrivateUserRole.Builder().present(false).build();
}
// Private access scope
switch(managedBy) {
case MANAGED_BY_APPLICATION:
{
// Supplying a user is optional for applications
if (inputUser == null) {
return new PrivateUserRole.Builder().present(false).build();
}
// We have a private user, so make sure the email is present and valid
String userEmail = commonFields.getPrivateResourceUser().getUserName();
ControllerValidationUtils.validateEmail(userEmail);
// Validate that the assigned user is a member of the workspace. It must have at least
// READ action.
SamRethrow.onInterrupted(() -> samService.userIsAuthorized(SamConstants.SamResource.WORKSPACE, workspaceId.toString(), SamConstants.SamWorkspaceAction.READ, userEmail, userRequest), "validate private user is workspace member");
// Translate the incoming role list into our internal model form
// This also validates that the incoming API model values are correct.
List<ControlledResourceIamRole> roles = commonFields.getPrivateResourceUser().getPrivateResourceIamRoles().stream().map(ControlledResourceIamRole::fromApiModel).collect(Collectors.toList());
if (roles.isEmpty()) {
throw new ValidationException("You must specify at least one role when you specify PrivateResourceIamRoles");
}
// The legal options for the assigned user of an application is READER
// or WRITER. EDITOR is not allowed. We take the "max" of READER and WRITER.
var maxRole = ControlledResourceIamRole.READER;
for (ControlledResourceIamRole role : roles) {
if (role == ControlledResourceIamRole.WRITER) {
if (maxRole == ControlledResourceIamRole.READER) {
maxRole = role;
}
} else if (role != ControlledResourceIamRole.READER) {
throw new ValidationException("For application private controlled resources, only READER and WRITER roles are allowed. Found " + role.toApiModel());
}
}
return new PrivateUserRole.Builder().present(true).userEmail(userEmail).role(maxRole).build();
}
case MANAGED_BY_USER:
{
// TODO: PF-1218 The target state is that supplying a user is not allowed.
// However, current CLI and maybe UI are supplying all or part of the structure,
// so tolerate all states: no-input, only roles, roles and user
/* Target state:
// Supplying a user is not allowed. The creating user is always the assigned user.
validateNoInputUser(inputUser);
*/
// Fill in the user role for the creating user
String userEmail = SamRethrow.onInterrupted(() -> samService.getUserEmailFromSam(userRequest), "getUserEmailFromSam");
// matches the requesting name.
if (inputUser != null && inputUser.getUserName() != null) {
if (!StringUtils.equalsIgnoreCase(userEmail, inputUser.getUserName())) {
throw new BadRequestException("User (" + userEmail + ") may only assign a private controlled resource to themselves");
}
}
// to different objects.
return new PrivateUserRole.Builder().present(true).userEmail(userEmail).role(ControlledResourceIamRole.EDITOR).build();
}
default:
throw new InternalLogicException("Unknown managedBy enum");
}
}
Aggregations