Skip to content

Commit

Permalink
ID-1341 Auth Domain Constraint Satisfaction Endpoint (#1502)
Browse files Browse the repository at this point in the history
* ID-1341 Auth Domain Constraint Satisfaction Endpoint

* change api-docs operationId

---------

Co-authored-by: Tristan Garwood <[email protected]>
  • Loading branch information
tlangs and Ghost-in-a-Jar authored Jul 29, 2024
1 parent 29debc4 commit 798f6cf
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 1 deletion.
36 changes: 36 additions & 0 deletions src/main/resources/swagger/api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,42 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/resources/v2/{resourceTypeName}/{resourceId}/authDomain/satisfied:
get:
tags:
- Resources
summary: Check whether the calling user satisfies all Authoriaztion Domain Constraints
operationId: isAuthDomainV2Satisfied
parameters:
- name: resourceTypeName
in: path
description: Type of resource
required: true
schema:
type: string
- name: resourceId
in: path
description: Id of resource
required: true
schema:
type: string
responses:
200:
description: User satisfies all authorization domain constraints.
Empty if an Auth Domain has not been set
403:
description: User does not satisfy all authorization domain constraints.
content: { }
404:
description: Resource type or resource does not exist or you are not a member
of any policy on the resource
content: { }
500:
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/resources/v2/{resourceTypeName}/{resourceId}/allUsers:
get:
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ trait ResourceRoutes extends SamUserDirectives with SecurityDirectives with SamM
pathEndOrSingleSlash {
getResourceAuthDomain(resource, samUser, samRequestContext) ~
patchResourceAuthDomain(resource, samUser, samRequestContext)
} ~
pathPrefix("satisfied") {
pathEndOrSingleSlash {
complete {
resourceService.satisfiesAuthDomainConstrains(resource, samUser, samRequestContext).map { satisfied =>
if (satisfied) StatusCodes.OK else StatusCodes.Forbidden
}
}
}
}
} ~
pathPrefix("roles") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ class ResourceService(
}
)

def satisfiesAuthDomainConstrains(resource: FullyQualifiedResourceId, samUser: SamUser, samRequestContext: SamRequestContext): IO[Boolean] =
loadResourceAuthDomain(resource, samRequestContext).flatMap { authDomain =>
authDomain.toList.traverse { group =>
policyEvaluatorService.hasPermission(
FullyQualifiedResourceId(ManagedGroupService.managedGroupTypeName, ResourceId(group.value)),
ManagedGroupService.useAction,
samUser.id,
samRequestContext
)
}
} map { listOfUsePermissions => listOfUsePermissions.isEmpty || listOfUsePermissions.forall(identity) }

def addResourceAuthDomain(
resource: FullyQualifiedResourceId,
authDomains: Set[WorkbenchGroupName],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,71 @@ class ResourceRoutesV2Spec extends RetryableAnyFlatSpec with Matchers with TestS
}
}

"GET /api/resources/v2/{resourceType}/{resourceId}/authDomain/satisfied" should "200 if the calling user satisfies the auth domain constraints" in {
val managedGroupResourceType = initManagedGroupResourceType()

val authDomain = "authDomain"
val resourceType = ResourceType(
ResourceTypeName("rt"),
Set(SamResourceActionPatterns.readAuthDomain, SamResourceActionPatterns.use),
Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.readAuthDomain, ManagedGroupService.useAction))),
ResourceRoleName("owner")
)
val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType, managedGroupResourceType.name -> managedGroupResourceType))

runAndWait(samRoutes.managedGroupService.createManagedGroup(ResourceId(authDomain), defaultUserInfo, samRequestContext = samRequestContext))

val resourceId = ResourceId("foo")
val policiesMap = Map(
AccessPolicyName("ap") -> AccessPolicyMembershipRequest(
Set(defaultUserInfo.email),
Set(SamResourceActions.readAuthDomain, ManagedGroupService.useAction),
Set(ResourceRoleName("owner"))
)
)
runAndWait(
samRoutes.resourceService
.createResource(resourceType, resourceId, policiesMap, Set(WorkbenchGroupName(authDomain)), None, defaultUserInfo.id, samRequestContext)
)

Get(s"/api/resources/v2/${resourceType.name}/${resourceId.value}/authDomain/satisfied") ~> samRoutes.route ~> check {
status shouldEqual StatusCodes.OK
}
}

it should "403 if the calling user does not satisfy the auth domain constraints" in {
val managedGroupResourceType = initManagedGroupResourceType()

val authDomain = "authDomain"
val resourceType = ResourceType(
ResourceTypeName("rt"),
Set(SamResourceActionPatterns.readAuthDomain, SamResourceActionPatterns.use),
Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.readAuthDomain, ManagedGroupService.useAction))),
ResourceRoleName("owner")
)
val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType, managedGroupResourceType.name -> managedGroupResourceType))
val otherUser = Generator.genWorkbenchUserGoogle.sample.get
runAndWait(samRoutes.userService.createUser(otherUser, samRequestContext))
runAndWait(samRoutes.managedGroupService.createManagedGroup(ResourceId(authDomain), otherUser, samRequestContext = samRequestContext))

val resourceId = ResourceId("foo")
val policiesMap = Map(
AccessPolicyName("ap") -> AccessPolicyMembershipRequest(
Set(defaultUserInfo.email),
Set(SamResourceActions.readAuthDomain, ManagedGroupService.useAction),
Set(ResourceRoleName("owner"))
)
)
runAndWait(
samRoutes.resourceService
.createResource(resourceType, resourceId, policiesMap, Set(WorkbenchGroupName(authDomain)), None, otherUser.id, samRequestContext)
)

Get(s"/api/resources/v2/${resourceType.name}/${resourceId.value}/authDomain/satisfied") ~> samRoutes.route ~> check {
status shouldEqual StatusCodes.Forbidden
}
}

private def initManagedGroupResourceType(): ResourceType = {
val accessPolicyNames = Set(ManagedGroupService.adminPolicyName, ManagedGroupService.memberPolicyName, ManagedGroupService.adminNotifierPolicyName)
val policyActions: Set[ResourceAction] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ class ResourceServiceSpec
}
}

it should "fail when user does not have access to at least 1 of the auth domain groups" in {
it should "fail when user does not have access all of the auth domain groups" in {
assume(databaseEnabled, databaseEnabledClue)

constrainableResourceType.isAuthDomainConstrainable shouldEqual true
Expand Down Expand Up @@ -913,6 +913,48 @@ class ResourceServiceSpec
}
}

it should "say auth domain is satisfied when a user is in all auth domain groups, and not satified when a user isn't" in {
assume(databaseEnabled, databaseEnabledClue)

constrainableResourceType.isAuthDomainConstrainable shouldEqual true
constrainableService.createResourceType(constrainableResourceType, samRequestContext).unsafeRunSync()

val bender = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(bender, samRequestContext).unsafeRunSync()

val fry = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(fry, samRequestContext).unsafeRunSync()

constrainableService.createResourceType(managedGroupResourceType, samRequestContext).unsafeRunSync()
val managedGroupName1 = "firstGroup"
runAndWait(managedGroupService.createManagedGroup(ResourceId(managedGroupName1), dummyUser, samRequestContext = samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName1), ManagedGroupService.adminPolicyName, bender.id, samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName1), ManagedGroupService.adminPolicyName, fry.id, samRequestContext))
val managedGroupName2 = "benderIsGreat"
runAndWait(managedGroupService.createManagedGroup(ResourceId(managedGroupName2), bender, samRequestContext = samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName2), ManagedGroupService.adminPolicyName, bender.id, samRequestContext))

val authDomain = Set(WorkbenchGroupName(managedGroupName1), WorkbenchGroupName(managedGroupName2))
val viewPolicyName = AccessPolicyName(constrainableReaderRoleName.value)
val resource = runAndWait(
constrainableService.createResource(
constrainableResourceType,
ResourceId(UUID.randomUUID().toString),
Map(viewPolicyName -> constrainablePolicyMembership),
authDomain,
None,
bender.id,
samRequestContext
)
)

val benderAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, bender, samRequestContext).unsafeRunSync()
val fryAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, fry, samRequestContext).unsafeRunSync()

benderAccess shouldEqual true
fryAccess shouldEqual false
}

"Loading an auth domain" should "fail when the resource does not exist" in {
assume(databaseEnabled, databaseEnabledClue)

Expand All @@ -924,6 +966,82 @@ class ResourceServiceSpec
e.getMessage should include("not found")
}

"Checking auth domain satisfaction" should "say auth domain is satisfied when a user is in all auth domain groups, and not satisfied when a user isn't" in {
assume(databaseEnabled, databaseEnabledClue)

constrainableResourceType.isAuthDomainConstrainable shouldEqual true
constrainableService.createResourceType(constrainableResourceType, samRequestContext).unsafeRunSync()

val bender = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(bender, samRequestContext).unsafeRunSync()

val fry = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(fry, samRequestContext).unsafeRunSync()

constrainableService.createResourceType(managedGroupResourceType, samRequestContext).unsafeRunSync()
val managedGroupName1 = "firstGroup"
runAndWait(managedGroupService.createManagedGroup(ResourceId(managedGroupName1), dummyUser, samRequestContext = samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName1), ManagedGroupService.adminPolicyName, bender.id, samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName1), ManagedGroupService.adminPolicyName, fry.id, samRequestContext))
val managedGroupName2 = "benderIsGreat"
runAndWait(managedGroupService.createManagedGroup(ResourceId(managedGroupName2), bender, samRequestContext = samRequestContext))
runAndWait(managedGroupService.addSubjectToPolicy(ResourceId(managedGroupName2), ManagedGroupService.adminPolicyName, bender.id, samRequestContext))

val authDomain = Set(WorkbenchGroupName(managedGroupName1), WorkbenchGroupName(managedGroupName2))
val viewPolicyName = AccessPolicyName(constrainableReaderRoleName.value)
val resource = runAndWait(
constrainableService.createResource(
constrainableResourceType,
ResourceId(UUID.randomUUID().toString),
Map(viewPolicyName -> constrainablePolicyMembership),
authDomain,
None,
bender.id,
samRequestContext
)
)

val benderAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, bender, samRequestContext).unsafeRunSync()
val fryAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, fry, samRequestContext).unsafeRunSync()

benderAccess shouldEqual true
fryAccess shouldEqual false
}

it should "say the auth domain is satisfied if there are no auth domain constraints" in {
assume(databaseEnabled, databaseEnabledClue)

constrainableResourceType.isAuthDomainConstrainable shouldEqual true
constrainableService.createResourceType(constrainableResourceType, samRequestContext).unsafeRunSync()

val bender = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(bender, samRequestContext).unsafeRunSync()

val fry = Generator.genWorkbenchUserBoth.sample.get
dirDAO.createUser(fry, samRequestContext).unsafeRunSync()

constrainableService.createResourceType(managedGroupResourceType, samRequestContext).unsafeRunSync()

val viewPolicyName = AccessPolicyName(constrainableReaderRoleName.value)
val resource = runAndWait(
constrainableService.createResource(
constrainableResourceType,
ResourceId(UUID.randomUUID().toString),
Map(viewPolicyName -> constrainablePolicyMembership),
Set.empty,
None,
bender.id,
samRequestContext
)
)

val benderAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, bender, samRequestContext).unsafeRunSync()
val fryAccess = constrainableService.satisfiesAuthDomainConstrains(resource.fullyQualifiedId, fry, samRequestContext).unsafeRunSync()

benderAccess shouldEqual true
fryAccess shouldEqual true
}

"Creating a resource that has 0 constrainable action patterns" should "fail when an auth domain is provided" in {
assume(databaseEnabled, databaseEnabledClue)

Expand Down

0 comments on commit 798f6cf

Please sign in to comment.