Skip to content

Commit

Permalink
implement listUserResourceActions
Browse files Browse the repository at this point in the history
  • Loading branch information
dvoet committed Sep 18, 2020
1 parent c4e0934 commit 24048f3
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,9 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
listResourcesWithAuthdomains(resourceId.resourceTypeName, Set(resourceId.resourceId), samRequestContext).map(_.headOption)
}

// used to list the the resources/policies a user has access to
// - policies may now come from parent - probably need to disambiguate policy name
// also used to list the managed groups but that does not matter for hierarchy
override def listAccessPolicies(resourceTypeName: ResourceTypeName, userId: WorkbenchUserId, samRequestContext: SamRequestContext): IO[Set[ResourceIdAndPolicyName]] = {
val ancestorGroupsTable = SubGroupMemberTable("ancestor_groups")
val ag = ancestorGroupsTable.syntax("ag")
Expand Down Expand Up @@ -742,6 +745,7 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
listPolicies(resource, samRequestContext = samRequestContext)
}

// only used to determine what actions a user has on the resource, could be rewritten as listUserResourceActions
override def listAccessPoliciesForUser(resource: FullyQualifiedResourceId, user: WorkbenchUserId, samRequestContext: SamRequestContext): IO[Set[AccessPolicyWithoutMembers]] = {
runInTransaction("listAccessPoliciesForUser", samRequestContext)({ implicit session =>
val ancestorGroupsTable = SubGroupMemberTable("ancestor_groups")
Expand Down Expand Up @@ -796,6 +800,69 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
})
}

def listUserResourceActions(resourceId: FullyQualifiedResourceId, user: WorkbenchUserId, samRequestContext: SamRequestContext): IO[Set[ResourceAction]] = {
runInTransaction("listAccessPoliciesForUser", samRequestContext)({ implicit session =>
val ancestorGroupsTable = SubGroupMemberTable("ancestor_groups")
val ancestorGroup = ancestorGroupsTable.syntax("ancestorGroup")
val agColumn = ancestorGroupsTable.column

val parentGroup = GroupMemberTable.syntax("parent_groups")
val resource = ResourceTable.syntax("resource")
val resourceType = ResourceTypeTable.syntax("resourceType")
val groupMember = GroupMemberTable.syntax("groupMember")
val policy = PolicyTable.syntax("policy")

val policyRole = PolicyRoleTable.syntax("policyRole")
val resourceRole = ResourceRoleTable.syntax("resourceRole")
val policyActionJoin = PolicyActionTable.syntax("policyActionJoin")
val policyAction = ResourceActionTable.syntax("policyAction")
val roleActionJoin = RoleActionTable.syntax("roleActionJoin")
val roleAction = ResourceActionTable.syntax("roleAction")

val ancestorResourceTable = AncestorResourceTable("ancestor_resource")
val ancestorResource = ancestorResourceTable.syntax("ancestorResource")
val arColumn = ancestorResourceTable.column
val parentResource = ResourceTable.syntax("parentResource")

val listUserResourceActionsQuery = samsql"""with recursive
${ancestorGroupsTable.table}(${agColumn.parentGroupId}) as (
select ${groupMember.groupId}
from ${GroupMemberTable as groupMember}
where ${groupMember.memberUserId} = ${user}
union
select ${parentGroup.groupId}
from ${GroupMemberTable as parentGroup}
join ${ancestorGroupsTable as ancestorGroup} on ${agColumn.parentGroupId} = ${parentGroup.memberGroupId}),

${ancestorResourceTable.table}(${arColumn.resourceId}, ${arColumn.isAncestor}, ${arColumn.baseResourceTypeId}) as (
select ${resource.id}, false, ${resourceType.id}
from ${ResourceTable as resource}
join ${ResourceTypeTable as resourceType} on ${resource.resourceTypeId} = ${resourceType.id}
where ${resource.name} = ${resourceId.resourceId}
and ${resourceType.name} = ${resourceId.resourceTypeName}
union
select ${parentResource.resourceParentId}, true, ${ancestorResource.baseResourceTypeId}
from ${ResourceTable as parentResource}
join ${ancestorResourceTable as ancestorResource} on ${ancestorResource.resourceId} = ${parentResource.id}
where ${parentResource.resourceParentId} is not null)

select ${roleAction.result.action}, ${policyAction.result.action}
from ${ancestorResourceTable as ancestorResource}
join ${PolicyTable as policy} on ${policy.resourceId} = ${ancestorResource.resourceId}
left join ${PolicyRoleTable as policyRole} on ${policy.id} = ${policyRole.resourcePolicyId} and ${ancestorResource.isAncestor} = ${policyRole.descends}
left join ${ResourceRoleTable as resourceRole} on ${policyRole.resourceRoleId} = ${resourceRole.id} and ${ancestorResource.baseResourceTypeId} = ${resourceRole.resourceTypeId}
left join ${RoleActionTable as roleActionJoin} on ${resourceRole.id} = ${roleActionJoin.resourceRoleId}
left join ${ResourceActionTable as roleAction} on ${roleActionJoin.resourceActionId} = ${roleAction.id}
left join ${PolicyActionTable as policyActionJoin} on ${policy.id} = ${policyActionJoin.resourcePolicyId} and ${ancestorResource.isAncestor} = ${policyActionJoin.descends}
left join ${ResourceActionTable as policyAction} on ${policyActionJoin.resourceActionId} = ${policyAction.id} and ${ancestorResource.baseResourceTypeId} = ${policyAction.resourceTypeId}
where ${policy.public} OR ${policy.groupId} in (select ${ancestorGroup.parentGroupId} from ${ancestorGroupsTable as ancestorGroup})"""

listUserResourceActionsQuery.map { rs =>
rs.stringOpt(roleAction.resultName.action).map(ResourceAction(_)) ++ rs.stringOpt(policyAction.resultName.action).map(ResourceAction(_))
}.list().apply().flatten.toSet
})
}

override def listFlattenedPolicyMembers(policyId: FullyQualifiedPolicyId, samRequestContext: SamRequestContext): IO[Set[WorkbenchUser]] = {
val subGroupMemberTable = SubGroupMemberTable("sub_group")
val sg = subGroupMemberTable.syntax("sg")
Expand Down Expand Up @@ -856,7 +923,7 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
val resourceTableColumn = ResourceTable.column

val query =
samsql"""with recursive ${ancestorResourceTable.table}(${arColumn.resourceParentId}) as (
samsql"""with recursive ${ancestorResourceTable.table}(${arColumn.resourceId}) as (
select ${r.id}
from ${ResourceTable as r}
join ${ResourceTypeTable as rt} on ${r.resourceTypeId} = ${rt.id}
Expand All @@ -865,7 +932,7 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
union
select ${pr.resourceParentId}
from ${ResourceTable as pr}
join ${ancestorResourceTable as ar} on ${ar.resourceParentId} = ${pr.id}
join ${ancestorResourceTable as ar} on ${ar.resourceId} = ${pr.id}
where ${pr.resourceParentId} is not null
) update ${ResourceTable as r}
set ${resourceTableColumn.resourceParentId} =
Expand All @@ -879,7 +946,7 @@ class PostgresAccessPolicyDAO(protected val dbRef: DbReference,
and ${r.name} = ${childResource.resourceId}
and ${rt.name} = ${childResource.resourceTypeName}
and ${r.id} not in
( select ${ar.resourceParentId}
( select ${ar.resourceId}
from ${ancestorResourceTable as ar} )"""

runInTransaction("setResourceParent", samRequestContext)({ implicit session =>
Expand Down Expand Up @@ -967,8 +1034,8 @@ object FullyQualifiedResourceAction {

// these 2 case classes represent the logical table used in recursive ancestor resource queries
// this table does not actually exist but looks like a table in a WITH RECURSIVE query
final case class AncestorResourceRecord(resourceParentId: ResourcePK)
final case class AncestorResourceRecord(resourceId: ResourcePK, isAncestor: Boolean, baseResourceTypeId: ResourceTypePK)
final case class AncestorResourceTable(override val tableName: String) extends SQLSyntaxSupport[AncestorResourceRecord] {
// need to specify column names explicitly because this table does not actually exist in the database
override val columnNames: Seq[String] = Seq("resource_parent_id")
override val columnNames: Seq[String] = Seq("resource_id", "is_ancestor", "base_resource_type_id")
}
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,114 @@ class PostgresAccessPolicyDAOSpec extends FreeSpec with Matchers with BeforeAndA
}
}

"listUserResourceActions" - {
"lists the actions on a resource that a user is a member of" in {
val user = WorkbenchUser(WorkbenchUserId("user"), None, WorkbenchEmail("user@user.edu"), None)

val subGroup = BasicWorkbenchGroup(WorkbenchGroupName("subGroup"), Set(user.id), WorkbenchEmail("sub@groups.com"))
val parentGroup = BasicWorkbenchGroup(WorkbenchGroupName("parent"), Set(subGroup.id), WorkbenchEmail("parent@groups.com"))

val resource = Resource(resourceType.name, ResourceId("resource"), Set.empty)
// backgroundPolicy makes sure there is something in the database that is excluded by the query
val backgroundPolicy = AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("background")), Set.empty, WorkbenchEmail("background@policy.com"), Set(ownerRole.roleName), Set.empty, Set.empty, false)

dirDao.createUser(user, samRequestContext).unsafeRunSync()
dirDao.createGroup(subGroup, samRequestContext = samRequestContext).unsafeRunSync()
dirDao.createGroup(parentGroup, samRequestContext = samRequestContext).unsafeRunSync()

dao.createResourceType(resourceType, samRequestContext).unsafeRunSync()
dao.createResource(resource, samRequestContext).unsafeRunSync()
dao.createPolicy(backgroundPolicy, samRequestContext).unsafeRunSync()

val probePolicies = List(
// user with role
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set(user.id), WorkbenchEmail("probe@policy.com"), Set(readerRole.roleName), Set.empty, Set.empty, false),

// user with action
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set(user.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set(readAction), Set.empty, false),

// public with role
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set.empty, WorkbenchEmail("probe@policy.com"), Set(readerRole.roleName), Set.empty, Set.empty, true),

//public with action
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set.empty, WorkbenchEmail("probe@policy.com"), Set.empty, Set(readAction), Set.empty, true),

// group with role
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set(parentGroup.id), WorkbenchEmail("probe@policy.com"), Set(readerRole.roleName), Set.empty, Set.empty, false),

// group with action
AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("probe")), Set(parentGroup.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set(readAction), Set.empty, false),
)

probePolicies.foreach { probePolicy =>
(for {
_ <- dao.deletePolicy(probePolicy.id, samRequestContext)
_ <- dao.createPolicy(probePolicy, samRequestContext)
result <- dao.listUserResourceActions(resource.fullyQualifiedId, user.id, samRequestContext)
} yield {
withClue(probePolicy) {
result should contain theSameElementsAs Set(readAction)
}
}).unsafeRunSync()
}
}

"lists the actions on a resource that a user is a member of via ancestor" in {
val user = WorkbenchUser(WorkbenchUserId("user"), None, WorkbenchEmail("user@user.edu"), None)

val subGroup = BasicWorkbenchGroup(WorkbenchGroupName("subGroup"), Set(user.id), WorkbenchEmail("sub@groups.com"))
val parentGroup = BasicWorkbenchGroup(WorkbenchGroupName("parent"), Set(subGroup.id), WorkbenchEmail("parent@groups.com"))

val parentResource = Resource(resourceType.name, ResourceId("parentResource"), Set.empty)
val resource = Resource(resourceType.name, ResourceId("resource"), Set.empty)
// backgroundPolicy makes sure there is something in the database that is excluded by the query
val backgroundPolicy = AccessPolicy(FullyQualifiedPolicyId(resource.fullyQualifiedId, AccessPolicyName("background")), Set.empty, WorkbenchEmail("background@policy.com"), Set(ownerRole.roleName), Set.empty, Set.empty, false)

dirDao.createUser(user, samRequestContext).unsafeRunSync()
dirDao.createGroup(subGroup, samRequestContext = samRequestContext).unsafeRunSync()
dirDao.createGroup(parentGroup, samRequestContext = samRequestContext).unsafeRunSync()

dao.createResourceType(resourceType, samRequestContext).unsafeRunSync()
dao.createResource(parentResource, samRequestContext).unsafeRunSync()
dao.createResource(resource, samRequestContext).unsafeRunSync()
dao.setResourceParent(resource.fullyQualifiedId, parentResource.fullyQualifiedId, samRequestContext).unsafeRunSync()
dao.createPolicy(backgroundPolicy, samRequestContext).unsafeRunSync()

val probePolicies = List(
// user with role
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set(user.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set.empty, Set(readerRole.roleName))), false),

//user with action
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set(user.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set(readAction), Set.empty)), false),

// public with role
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set.empty, WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set.empty, Set(readerRole.roleName))), true),

//public with action
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set.empty, WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set(readAction), Set.empty)), true),

// group with role
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set(parentGroup.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set.empty, Set(readerRole.roleName))), false),

// group with action
AccessPolicy(FullyQualifiedPolicyId(parentResource.fullyQualifiedId, AccessPolicyName("probe")), Set(parentGroup.id), WorkbenchEmail("probe@policy.com"), Set.empty, Set.empty, Set(AccessPolicyDescendantPermissions(resourceType.name, Set(readAction), Set.empty)), false),
)

probePolicies.foreach { probePolicy =>
(for {
_ <- dao.deletePolicy(probePolicy.id, samRequestContext)
_ <- dao.createPolicy(probePolicy, samRequestContext)
childResult <- dao.listUserResourceActions(resource.fullyQualifiedId, user.id, samRequestContext)
parentResult <- dao.listUserResourceActions(parentResource.fullyQualifiedId, user.id, samRequestContext)
} yield {
withClue(probePolicy) {
childResult should contain theSameElementsAs Set(readAction)
parentResult shouldBe empty
}
}).unsafeRunSync()
}
} }

"setPolicyIsPublic" - {
"can change whether a policy is public or private" in {
val resource = Resource(resourceType.name, ResourceId("resource"), Set.empty)
Expand Down

0 comments on commit 24048f3

Please sign in to comment.