Skip to content

Commit

Permalink
ID-1369 Admin endpoint repair access (#1528)
Browse files Browse the repository at this point in the history
* ID-1369 New admin endpoint - repair cloud access.

* ID-1369 New admin endpoint - repair cloud access.

* fix test
  • Loading branch information
Ghost-in-a-Jar authored Aug 28, 2024
1 parent 524fbc0 commit b5048c1
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 4 deletions.
28 changes: 28 additions & 0 deletions src/main/resources/swagger/api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,34 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/admin/v2/user/{userId}/repairCloudAccess:
get:
tags:
- Admin
summary: Ensures that a user's proxy group exists and that it is added to any google groups that it should be in.
operationId: adminRepairUserCloudAccess
parameters:
- name: userId
in: path
description: User ID of the user to have their cloud access repaired
required: true
schema:
type: string
responses:
204:
description: User access was repaired successfully (as long as the group sync messages succeed)
403:
description: You do not have admin privileges
content: { }
404:
description: User not found
content: { }
500:
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorReport'
/api/admin/v1/user/email/{email}:
get:
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ trait AdminRoutes extends SecurityDirectives with SamRequestContextDirectives wi
.map(user => (if (user.isDefined) OK else NotFound) -> user)
}
}
} ~
pathPrefix("repairCloudAccess") {
pathEndOrSingleSlash {
putWithTelemetry(samRequestContext, userIdParam(workbenchUserId)) {
complete {
userService
.repairCloudAccess(workbenchUserId, samRequestContext)
.map(_ => NoContent)
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,19 @@ class UserService(

def setUserAttributes(userAttributes: SamUserAttributes, samRequestContext: SamRequestContext): IO[SamUserAttributes] =
directoryDAO.setUserAttributes(userAttributes, samRequestContext).map(_ => userAttributes)

def repairCloudAccess(workbenchUserId: WorkbenchUserId, samRequestContext: SamRequestContext): IO[Unit] = {
val maybeUser = getUser(workbenchUserId, samRequestContext)
maybeUser.flatMap {
case Some(user) =>
for {
_ <- cloudExtensions.onUserCreate(user, samRequestContext)
groups <- directoryDAO.listUserDirectMemberships(user.id, samRequestContext)
_ <- cloudExtensions.onGroupUpdate(groups, Set(user.id), samRequestContext)
} yield IO.pure(())
case None => IO.raiseError(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.NotFound, s"User $workbenchUserId not found")))
}
}
}

object UserService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,32 @@ class AdminUserRoutesSpec extends AnyFlatSpec with Matchers with ScalatestRouteT
status shouldEqual StatusCodes.NotFound
}
}

"PUT /admin/v2/user/{userId}/repairCloudAccess" should "return no content when called as admin user" in {
// Arrange
val samRoutes = new MockSamRoutesBuilder(allUsersGroup)
.callAsAdminUser() // enabled "admin" user who is making the http request
.withEnabledUser(defaultUser) // "persisted/enabled" user we will check the status of
.withAllowedUser(defaultUser)
.build

// Act and Assert
Put(s"/api/admin/v2/user/$defaultUserId/repairCloudAccess") ~> samRoutes.route ~> check {
status shouldEqual StatusCodes.NoContent
}
}

it should "forbid the request when the requesting user is a non admin" in {
// Arrange
val samRoutes = new MockSamRoutesBuilder(allUsersGroup)
.withEnabledUser(defaultUser) // "persisted/enabled" user we will check the status of
.withAllowedUser(defaultUser)
.callAsNonAdminUser()
.build

// Act and Assert
Put(s"/api/admin/v2/user/$defaultUserId/repairCloudAccess") ~> samRoutes.route ~> check {
status shouldEqual StatusCodes.Forbidden
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ class MockUserService(
def setGoogleSubjectId(userId: WorkbenchUserId, googleSubjectId: GoogleSubjectId, samRequestContext: SamRequestContext): IO[Unit] =
directoryDAO.setGoogleSubjectId(userId, googleSubjectId, samRequestContext)

def listUserDirectMemberships(userId: WorkbenchUserId, samRequestContext: SamRequestContext): IO[LazyList[WorkbenchGroupIdentity]] =
directoryDAO.listUserDirectMemberships(userId, samRequestContext)

def listFlattenedGroupMembers(groupName: WorkbenchGroupName, samRequestContext: SamRequestContext): IO[Set[WorkbenchUserId]] =
directoryDAO.listFlattenedGroupMembers(groupName, samRequestContext)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ case class MockUserServiceBuilder() extends IdiomaticMockito {
SamUserAllowances(enabled = false, termsOfService = false)
)
mockUserService.getUserAttributes(any[WorkbenchUserId], any[SamRequestContext]) returns IO(None)

mockUserService.repairCloudAccess(any[WorkbenchUserId], any[SamRequestContext]) returns IO(())
}

private def makeUser(samUser: SamUser, mockUserService: UserService): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import cats.effect.IO
import cats.effect.unsafe.implicits.{global => globalEc}
import org.broadinstitute.dsde.workbench.model._
import org.broadinstitute.dsde.workbench.sam.Generator.{arbNonPetEmail => _, _}
import org.broadinstitute.dsde.workbench.sam.TestSupport.{databaseEnabled, databaseEnabledClue, truncateAll}
import org.broadinstitute.dsde.workbench.sam.TestSupport.{databaseEnabled, databaseEnabledClue, googleServicesConfig, truncateAll}
import org.broadinstitute.dsde.workbench.sam.dataAccess.{DirectoryDAO, PostgresDirectoryDAO}
import org.broadinstitute.dsde.workbench.sam.google.GoogleExtensions
import org.broadinstitute.dsde.workbench.sam.matchers.BeSameUserMatcher.beSameUserAs
Expand Down Expand Up @@ -715,4 +715,40 @@ class OldUserServiceSpec(_system: ActorSystem)
assert(service.validateEmailAddress(WorkbenchEmail("[email protected]"), Seq.empty, Seq("bar.com")).attempt.unsafeRunSync().isLeft)
assert(service.validateEmailAddress(WorkbenchEmail("[email protected]"), Seq.empty, Seq("bar.com")).attempt.unsafeRunSync().isLeft)
}

"UserService repairCloudAccess" should "create a proxy group for a user and add it to any groups they are a member of" in {
assume(databaseEnabled, databaseEnabledClue)

// Create user
val inviteeEmail = genNonPetEmail.sample.get
service.inviteUser(inviteeEmail, samRequestContext).unsafeRunSync()
val invitedUserId = dirDAO.loadSubjectFromEmail(inviteeEmail, samRequestContext).unsafeRunSync().value.asInstanceOf[WorkbenchUserId]

val userInPostgres = dirDAO.loadUser(invitedUserId, samRequestContext).unsafeRunSync()
userInPostgres.value should {
equal(SamUser(invitedUserId, None, inviteeEmail, None, false))
}

val registeringUser = genWorkbenchUserGoogle.sample.get.copy(email = inviteeEmail)
runAndWait(service.createUser(registeringUser, samRequestContext))

verify(googleExtensions).onUserCreate(SamUser(invitedUserId, None, inviteeEmail, None, false), samRequestContext)

verify(googleExtensions).onGroupUpdate(Seq.empty, Set(invitedUserId), samRequestContext)

val updatedUserInPostgres = dirDAO.loadUser(invitedUserId, samRequestContext).unsafeRunSync()
updatedUserInPostgres.value shouldBe SamUser(invitedUserId, registeringUser.googleSubjectId, inviteeEmail, None, true)

val proxyGroup =
WorkbenchGroupName(s"${googleServicesConfig.resourceNamePrefix.getOrElse("")}PROXY_${invitedUserId.value}@${googleServicesConfig.appsDomain}")
// delete proxy group
runAndWait(dirDAO.deleteGroup(proxyGroup, samRequestContext))

// Run test
service.repairCloudAccess(invitedUserId, samRequestContext).unsafeRunSync()

verify(googleExtensions).onUserCreate(SamUser(invitedUserId, registeringUser.googleSubjectId, inviteeEmail, None, true), samRequestContext)

verify(googleExtensions).onGroupUpdate(Seq(allUsersGroup.id), Set(invitedUserId), samRequestContext)
}
}

0 comments on commit b5048c1

Please sign in to comment.