-
Notifications
You must be signed in to change notification settings - Fork 4
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
Conversation
@@ -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, |
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 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).
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.
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).
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 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
@@ -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, |
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.
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, |
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 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) { |
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.
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.
@@ -1075,53 +1143,6 @@ class WorkspaceService( | |||
.void | |||
} | |||
|
|||
private def validateAclChanges(aclChanges: Set[WorkspaceACLUpdate], |
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.
Moved inside of updateACL
since it's only called from there
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") | ||
) | ||
} |
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.
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 => |
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.
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) | |||
} | |||
|
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.
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
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) | ||
} |
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.
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" |
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.
Other than the exceptions at the bottom, these tests were moved over to WorkspaceServiceUpdateAclSpec
and modified to mock the AclManager
s 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)))) { |
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 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.
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 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.
Ticket: CORE-49
share_policy::_
permissions to make the requested ACL change.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 existingupdateAcl
tests into unit tests and moved them into a contained test file calledWorkspaceServiceUpdateAclSpec
.