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

[WOR-432][CORE-49] Add additional validation for updateAcl (take 2) #3072

Merged
merged 11 commits into from
Oct 29, 2024

Conversation

cahrens
Copy link
Contributor

@cahrens cahrens commented Oct 7, 2024

Ticket: CORE-49

  • This reverts Revert "[WOR-432] Add additional validation for updateAcl" #3065 and modifies the code to list the actions that the user has on the workspace. Rawls checks that the user has the necessary share_policy::_ permissions to make the requested ACL change.
  • The change to list the user's workspace actions broke a lot of older unit tests that relied on the database and a test implementation of Sam MockSamDAO. We are trying to reduce unit test reliance on the database in Rawls as these tests are slow and modifying the test implementation of Sam can be difficult. Given this, I converted existing updateAcl tests into unit tests and moved them into a contained test file called WorkspaceServiceUpdateAclSpec.

@@ -976,7 +975,11 @@ class WorkspaceService(

// figure out which of the incoming aclUpdates are actually changes by removing all the existingAcls
aclChanges = normalize(aclUpdates) -- existingAcls
_ = validateAclChanges(aclChanges, existingAcls, workspace)
callingUserRoles <- samDAO.listUserRolesForResource(SamResourceTypeNames.workspace,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to just rely on what is in existingAcls, but if the calling user has the projectOwner role, they don't appear directly in existingAcls with their email address… instead there is a group for projectOwner, and presumably the calling user is a member of that group, but I don't know how to get that information.

This solution isn't ideal though because it does introduce another Sam call (though this method isn't called often, so that's a minor issue) and it breaks the pattern of going through the workspaceAclManager (though the Sam call could be hidden by adding a pass-through method to that interface, if folks felt like it was worth it).

Copy link
Contributor

Choose a reason for hiding this comment

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

The additional sam call isn't ideal, but this does have the benefit of working in cases where the user has access to the workspace via group membership more generally than just with the project-owner case (e.g. a group was added as a writer on the workspace).

Copy link
Contributor

Choose a reason for hiding this comment

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

We could add a new Sam API that returned a flattened policy membership for a resource if we wanted to, but we would still need the direct policy membership to calculate the ACL changes

@cahrens cahrens changed the title Revert "Revert "[WOR-432] Add additional validation for updateAcl"" [WOR-432] Add additional validation for updateAcl (take 2) Oct 7, 2024
@@ -976,7 +975,11 @@ class WorkspaceService(

// figure out which of the incoming aclUpdates are actually changes by removing all the existingAcls
aclChanges = normalize(aclUpdates) -- existingAcls
_ = validateAclChanges(aclChanges, existingAcls, workspace)
callingUserRoles <- samDAO.listUserRolesForResource(SamResourceTypeNames.workspace,
Copy link
Contributor

Choose a reason for hiding this comment

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

The additional sam call isn't ideal, but this does have the benefit of working in cases where the user has access to the workspace via group membership more generally than just with the project-owner case (e.g. a group was added as a writer on the workspace).

@@ -976,7 +975,11 @@ class WorkspaceService(

// figure out which of the incoming aclUpdates are actually changes by removing all the existingAcls
aclChanges = normalize(aclUpdates) -- existingAcls
_ = validateAclChanges(aclChanges, existingAcls, workspace)
callingUserRoles <- samDAO.listUserRolesForResource(SamResourceTypeNames.workspace,
Copy link
Contributor

Choose a reason for hiding this comment

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

We could add a new Sam API that returned a flattened policy membership for a resource if we wanted to, but we would still need the direct policy membership to calculate the ACL changes

// be removing as well as what we are adding.
val allRolePermissionChanges =
aclChanges ++ existingAcls.filter(existingAcl => emailsBeingChanged.contains(existingAcl.email.toLowerCase))
if (callingUserMaxRole < WorkspaceAccessLevels.Owner) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This makes assumptions about the actions contained in the workspace roles defined in Sam's resource type configuration, which isn't ideal as the name of a role in Sam shouldn't be used for authz control. Given that, this could be modified to check actions directly to make sure the user has the necessary share_policy::_ actions for the requested ACL changes which would be the right thing.

Still, I don't know if that's strictly necessary here, but happy to discuss. This check isn't intended to be true authorization as that is still handled by Sam -- the goal here is just to provide some guard rails and prevent ACL changes we believe are unlikely to succeed.

@marctalbott marctalbott marked this pull request as ready for review October 16, 2024 19:41
@marctalbott marctalbott changed the title [WOR-432] Add additional validation for updateAcl (take 2) [WOR-432][CORE-49] Add additional validation for updateAcl (take 2) Oct 16, 2024
@@ -1075,53 +1143,6 @@ class WorkspaceService(
.void
}

private def validateAclChanges(aclChanges: Set[WorkspaceACLUpdate],
Copy link
Contributor

Choose a reason for hiding this comment

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

Moved inside of updateACL since it's only called from there

Comment on lines +964 to +979
if (callingUserActions.isEmpty) {
throw new InvalidWorkspaceAclUpdateException(
ErrorReport(StatusCodes.BadRequest, "you do not have access to change permissions for this workspace")
)
}
// Add the existingAcl entries that are being modified so we can check what we will
// be removing as well as what we are adding.
val changingPolicies =
(aclChanges ++ existingAcls.filter(existingAcl => emailsBeingChanged.contains(existingAcl.email.toLowerCase)))
.flatMap(aclUpdateToPolicies)

if (changingPolicies.exists(policy => !callingUserActions.contains(SamWorkspaceActions.sharePolicy(policy.value)))) {
throw new InvalidWorkspaceAclUpdateException(
ErrorReport(StatusCodes.BadRequest, "you do not have sufficient permissions to make these changes")
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These changes and the new call to Sam to list user actions on the workspace are the functional changes this PR is making

}
}

it should "not clobber catalog permission" in withTestDataServicesCustomSam { services =>
Copy link
Contributor

Choose a reason for hiding this comment

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

This wasn't moved because WorkspaceService doesn't actually do anything to prevent the clobbering of catalog permissions. The RawlsWorkspaceAclManager filters out the catalog policy when listing policies, so a new unit test was added to RawlsWorkspaceAclManagerUnitTests.

@@ -497,244 +497,6 @@ class WorkspaceServiceSpec
Await.result(populateAcl, Duration.Inf)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

In general, these tests were moved to WorkspaceServiceUpdateAclSpec and adjusted to remove their reliance on database and CustomizableSamDao. The one exception is called out below

Comment on lines -1754 to -1812
it should "not allow readers to have compute access but should allow writers for Rawls workspaces" in {
val projectOwnerEmail = "[email protected]"
val ownerEmail = "[email protected]"
val writerEmail = "[email protected]"
val readerEmail = "[email protected]"

val samDAO = mockSamForAclTests()
when(samDAO.listPoliciesForResource(ArgumentMatchers.eq(SamResourceTypeNames.workspace), any(), any()))
.thenReturn(Future(samWorkspacePoliciesForAclTests(projectOwnerEmail, ownerEmail, writerEmail, readerEmail)))
when(samDAO.addUserToPolicy(any(), any(), any(), any(), any())).thenReturn(Future())

val workspaceRepository = mockWorkspaceRepositoryForAclTests(WorkspaceType.RawlsWorkspace)
val mockFastPassService = mock[FastPassService]
when(mockFastPassService.syncFastPassesForUserInWorkspace(any[Workspace], any[String])).thenReturn(Future())

val service = workspaceServiceConstructor(workspaceRepository = workspaceRepository,
samDAO = samDAO,
fastPassServiceConstructor = _ => mockFastPassService
)(ctx)

val writerAclUpdate = Set(WorkspaceACLUpdate(writerEmail, WorkspaceAccessLevels.Write, Option(false), Option(true)))
Await.result(service.updateACL(WorkspaceName("fake_namespace", "fake_name"), writerAclUpdate, true), Duration.Inf)

val readerAclUpdate = Set(WorkspaceACLUpdate(readerEmail, WorkspaceAccessLevels.Read, Option(false), Option(true)))

val thrown = intercept[RawlsExceptionWithErrorReport] {
Await.result(service.updateACL(WorkspaceName("fake_namespace", "fake_name"), readerAclUpdate, true), Duration.Inf)
}
thrown.errorReport.statusCode shouldBe Option(StatusCodes.BadRequest)
}

it should "allow readers with and without share access for Rawls workspaces" in {
val projectOwnerEmail = "[email protected]"
val ownerEmail = "[email protected]"
val writerEmail = "[email protected]"
val readerEmail = "[email protected]"

val samDAO = mockSamForAclTests()
when(samDAO.listPoliciesForResource(ArgumentMatchers.eq(SamResourceTypeNames.workspace), any(), any())).thenReturn(
Future(samWorkspacePoliciesForAclTests(projectOwnerEmail, ownerEmail, writerEmail, readerEmail))
)
when(samDAO.addUserToPolicy(any(), any(), any(), any(), any())).thenReturn(Future())

val workspaceRepository = mockWorkspaceRepositoryForAclTests(WorkspaceType.RawlsWorkspace)
val mockFastPassService = mock[FastPassService]
when(mockFastPassService.syncFastPassesForUserInWorkspace(any[Workspace], any[String])).thenReturn(Future())

val service = workspaceServiceConstructor(workspaceRepository = workspaceRepository,
samDAO = samDAO,
fastPassServiceConstructor = _ => mockFastPassService
)(ctx)

val aclUpdate = Set(
WorkspaceACLUpdate(readerEmail, WorkspaceAccessLevels.Read, Option(true), Option(false)),
WorkspaceACLUpdate("[email protected]", WorkspaceAccessLevels.Read, Option(false), Option(false))
)

Await.result(service.updateACL(WorkspaceName("fake_namespace", "fake_name"), aclUpdate, true), Duration.Inf)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These tests are covered by the various permutation tests, so they weren't moved over to WorkspaceServiceUpdateAclSpec

@@ -1570,247 +1572,6 @@ class WorkspaceServiceUnitTests
verify(wsmDAO).getRoles(any(), any())
}

behavior of "updateAcl"
Copy link
Contributor

Choose a reason for hiding this comment

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

Other than the exceptions at the bottom, these tests were moved over to WorkspaceServiceUpdateAclSpec and modified to mock the AclManagers instead of Sam, WSM, and the database.

(aclChanges ++ existingAcls.filter(existingAcl => emailsBeingChanged.contains(existingAcl.email.toLowerCase)))
.flatMap(aclUpdateToPolicies)

if (changingPolicies.exists(policy => !callingUserActions.contains(SamWorkspaceActions.sharePolicy(policy.value)))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't grant alter_policies to workspace users so it should not be necessary to check that here. Just calling it out here in case anyone disagrees.

Copy link
Contributor

Choose a reason for hiding this comment

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

➕ I also don't foresee us changing that model anytime soon since it'd let users modify the project owners policy which would not be great.

@marctalbott marctalbott merged commit 5e862ec into develop Oct 29, 2024
29 checks passed
@marctalbott marctalbott deleted the revert-3065-revert-3064-WOR-432-rawls branch October 29, 2024 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants