diff --git a/core/src/main/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceImpl.scala b/core/src/main/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceImpl.scala index f6d15a2616..280e2a2972 100644 --- a/core/src/main/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceImpl.scala +++ b/core/src/main/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceImpl.scala @@ -3,6 +3,7 @@ package org.broadinstitute.dsde.rawls.fastpass import akka.http.scaladsl.model.StatusCodes import cats.effect.IO import cats.effect.unsafe.implicits.global +import cats.implicits.toTraverseOps import com.typesafe.scalalogging.LazyLogging import org.broadinstitute.dsde.rawls.RawlsExceptionWithErrorReport import org.broadinstitute.dsde.rawls.config.FastPassConfig @@ -215,8 +216,14 @@ object FastPassServiceImpl extends LazyLogging { def getEmailFromPetSaKey(petSaKey: String): WorkbenchEmail = WorkbenchEmail(petSaKey.parseJson.asJsObject.fields("client_email").asInstanceOf[JsString].value) - private case class UserAndPetEmails(userEmail: WorkbenchEmail, userType: IamMemberType, petEmail: WorkbenchEmail) { - override def toString: String = s"${userType.value}:${userEmail.value} and Pet:${petEmail.value}" + private case class UserAndPetEmails(userEmail: WorkbenchEmail, + userType: IamMemberType, + petEmails: Set[WorkbenchEmail] + ) { + override def toString: String = + s"${userType.value}:${userEmail.value} and Pets:[${petEmails.map(_.value).mkString(",")}]" + def toSeq: Seq[(WorkbenchEmail, IamMemberType)] = + Seq((userEmail, userType)) ++ petEmails.map((_, IamMemberTypes.ServiceAccount)) } } @@ -266,11 +273,13 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, if maybeUserStatus.isDefined samUserInfo = maybeUserStatus.map(SamUserInfo.fromSamUserStatus).orNull - petEmail <- DBIO.from(samDAO.getUserPetServiceAccount(ctx, childWorkspace.googleProjectId)) + projectPetEmail <- DBIO.from(samDAO.getUserPetServiceAccount(ctx, childWorkspace.googleProjectId)) + defaultPetKey <- DBIO.from(samDAO.getDefaultPetServiceAccountKeyForUser(ctx)) + defaultPetEmail = FastPassServiceImpl.getEmailFromPetSaKey(defaultPetKey) userType = getUserType(samUserInfo.userEmail) - userAndPet = UserAndPetEmails(samUserInfo.userEmail, userType, petEmail) + userAndPets = UserAndPetEmails(samUserInfo.userEmail, userType, Set(projectPetEmail, defaultPetEmail)) _ <- removeParentBucketReaderGrant(parentWorkspace, samUserInfo) - _ <- addFastPassGrantsForRoles(samUserInfo, userAndPet, parentWorkspace, Set(SamWorkspaceRoles.reader), ctx) + _ <- addFastPassGrantsForRoles(samUserInfo, userAndPets, parentWorkspace, Set(SamWorkspaceRoles.reader), ctx) } yield () } .transform { @@ -325,10 +334,11 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, userType = getUserType(samUserInfo.userEmail) workspaceRoles <- DBIO .from(samDAO.listUserRolesForResource(SamResourceTypeNames.workspace, workspace.workspaceId, defaultPetCtx)) - useDefaultPet = workspaceRoles.intersect(SamWorkspaceRoles.rolesContainingWritePermissions).isEmpty - petEmail <- - if (useDefaultPet) { - DBIO.successful(FastPassServiceImpl.getEmailFromPetSaKey(defaultPetSAJson)) + onlyUseDefaultPet = workspaceRoles.intersect(SamWorkspaceRoles.rolesContainingWritePermissions).isEmpty + defaultPetEmail <- DBIO.successful(FastPassServiceImpl.getEmailFromPetSaKey(defaultPetSAJson)) + projectPetEmail <- + if (onlyUseDefaultPet) { + DBIO.successful(Set.empty[WorkbenchEmail]) } else { DBIO.from( samDAO @@ -336,11 +346,12 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, RawlsUserEmail(samUserInfo.userEmail.value) ) .map(FastPassServiceImpl.getEmailFromPetSaKey) + .map(Set(_)) ) } - userAndPet = UserAndPetEmails(samUserInfo.userEmail, userType, petEmail) + userAndPets = UserAndPetEmails(samUserInfo.userEmail, userType, projectPetEmail + defaultPetEmail) - _ <- addFastPassGrantsForRoles(samUserInfo, userAndPet, workspace, workspaceRoles, defaultPetCtx) + _ <- addFastPassGrantsForRoles(samUserInfo, userAndPets, workspace, workspaceRoles, defaultPetCtx) } yield () } .transform { @@ -357,7 +368,7 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, } def addFastPassGrantsForRoles(samUserInfo: SamUserInfo, - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, workspace: Workspace, roles: Set[SamResourceRole], rawlsRequestContext: RawlsRequestContext @@ -371,20 +382,20 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, ) val expirationDate = OffsetDateTime.now(ZoneOffset.UTC).plus(config.grantPeriod) (for { - _ <- setupProjectRoles(workspace, roles, userAndPet, samUserInfo, expirationDate) - _ <- setupBucketRoles(workspace, roles, userAndPet, samUserInfo, expirationDate) + _ <- setupProjectRoles(workspace, roles, userAndPets, samUserInfo, expirationDate) + _ <- setupBucketRoles(workspace, roles, userAndPets, samUserInfo, expirationDate) } yield ()).cleanUp { case Some(throwable) => for { _ <- DBIO.from( - removeFastPassProjectGrants(userAndPet, + removeFastPassProjectGrants(userAndPets, roles.flatMap(samWorkspaceRoleToGoogleProjectIamRoles), workspace.googleProjectId ) ) _ <- DBIO.from( removeFastPassBucketGrants(workspace.bucketName, - userAndPet, + userAndPets, roles.flatMap(samWorkspaceRolesToGoogleBucketIamRoles), workspace.googleProjectId ) @@ -447,7 +458,7 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, private def setupProjectRoles(workspace: Workspace, samResourceRoles: Set[SamResourceRole], - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, samUserInfo: SamUserInfo, expirationDate: OffsetDateTime )(implicit dataAccess: DataAccess): ReadWriteAction[Unit] = { @@ -458,7 +469,7 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, for { _ <- writeGrantsToDb( workspace.workspaceId, - userAndPet, + userAndPets, samUserInfo.userSubjectId, gcpResourceType = IamResourceTypes.Project, workspace.googleProjectId.value, @@ -466,7 +477,7 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, expirationDate ) _ <- DBIO.from( - addUserAndPetToProjectIamRoles(workspace.googleProjectId, projectIamRoles, userAndPet, condition) + addUserAndPetToProjectIamRoles(workspace.googleProjectId, projectIamRoles, userAndPets, condition) ) } yield () } else { @@ -509,26 +520,25 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, } protected def writeGrantsToDb(workspaceId: String, - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, samUserSubjectId: WorkbenchUserId, gcpResourceType: IamResourceType, resourceName: String, organizationRoles: Set[String], expiration: OffsetDateTime )(implicit dataAccess: DataAccess): ReadWriteAction[Unit] = { - val rolesToWrite = - Seq((userAndPet.userEmail, userAndPet.userType), (userAndPet.petEmail, IamMemberTypes.ServiceAccount)).flatMap( - tuple => organizationRoles.map(r => (tuple._1, tuple._2, r)) - ) - DBIO.seq(rolesToWrite.map { tuple => + val rolesToWrite = userAndPets.toSeq.flatMap { case (email, memberType) => + organizationRoles.map(r => (email, memberType, r)) + } + DBIO.seq(rolesToWrite.map { case (email, memberType, role) => val fastPassGrant = FastPassGrant.newFastPassGrant( workspaceId, samUserSubjectId, - WorkbenchEmail(tuple._1.value), - tuple._2, + WorkbenchEmail(email.value), + memberType, gcpResourceType, resourceName, - tuple._3, + role, expiration ) @@ -539,128 +549,116 @@ class FastPassServiceImpl(protected val ctx: RawlsRequestContext, private def addUserAndPetToProjectIamRoles(googleProjectId: GoogleProjectId, organizationRoles: Set[String], - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, condition: Expr ): Future[Unit] = { logger.info( - s"Adding project-level FastPass access for $userAndPet in ${googleProjectId.value} [${organizationRoles.mkString(" ")}]" + s"Adding project-level FastPass access for $userAndPets in ${googleProjectId.value} [${organizationRoles.mkString(" ")}]" ) - for { - _ <- googleIamDAO.addRoles( - GoogleProject(googleProjectId.value), - userAndPet.userEmail, - userAndPet.userType, - organizationRoles, - condition = Some(condition) - ) - _ <- MetricsHelper.incrementFastPassGrantedCounter(userAndPet.userType).unsafeToFuture() - _ <- googleIamDAO.addRoles( - GoogleProject(googleProjectId.value), - userAndPet.petEmail, - IamMemberTypes.ServiceAccount, - organizationRoles, - condition = Some(condition) - ) - _ <- MetricsHelper.incrementFastPassGrantedCounter(IamMemberTypes.ServiceAccount).unsafeToFuture() - } yield () + // note that IO traverse is serial which is good because parallel IAM updates will fight with each other + userAndPets.toSeq + .traverse { case (email, memberType) => + for { + _ <- IO.fromFuture( + IO( + googleIamDAO.addRoles( + GoogleProject(googleProjectId.value), + email, + memberType, + organizationRoles, + condition = Some(condition) + ) + ) + ) + _ <- MetricsHelper.incrementFastPassGrantedCounter(memberType) + } yield () + } + .void + .unsafeToFuture() } private def addUserAndPetToBucketIamRole(gcsBucketName: GcsBucketName, organizationRoles: Set[String], - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, condition: Expr, googleProjectId: GoogleProjectId ): Future[Unit] = { logger.info( - s"Adding bucket-level FastPass access for $userAndPet in ${gcsBucketName.value} [${organizationRoles.mkString(" ")}]" + s"Adding bucket-level FastPass access for $userAndPets in ${gcsBucketName.value} [${organizationRoles.mkString(" ")}]" ) - for { - _ <- googleStorageDAO.addIamRoles( - gcsBucketName, - userAndPet.userEmail, - userAndPet.userType, - organizationRoles, - condition = Some(condition), - userProject = Some(GoogleProject(googleProjectId.value)) - ) - _ <- MetricsHelper.incrementFastPassGrantedCounter(userAndPet.userType).unsafeToFuture() - _ <- googleStorageDAO.addIamRoles( - gcsBucketName, - userAndPet.petEmail, - IamMemberTypes.ServiceAccount, - organizationRoles, - condition = Some(condition), - userProject = Some(GoogleProject(googleProjectId.value)) - ) - _ <- MetricsHelper.incrementFastPassGrantedCounter(IamMemberTypes.ServiceAccount).unsafeToFuture() - } yield () + // note that IO traverse is serial which is good because parallel IAM updates will fight with each other + userAndPets.toSeq + .traverse { case (email, memberType) => + for { + _ <- IO.fromFuture( + IO( + googleStorageDAO.addIamRoles( + gcsBucketName, + email, + memberType, + organizationRoles, + condition = Some(condition), + userProject = Some(GoogleProject(googleProjectId.value)) + ) + ) + ) + _ <- MetricsHelper.incrementFastPassGrantedCounter(memberType) + } yield () + } + .void + .unsafeToFuture() } - private def removeFastPassProjectGrants(userAndPet: UserAndPetEmails, + private def removeFastPassProjectGrants(userAndPets: UserAndPetEmails, organizationRoles: Set[String], googleProjectId: GoogleProjectId - ): Future[Unit] = { - - val userRemoval = () => - removeFastPassGrants( - IamResourceTypes.Project, - googleProjectId.value, - userAndPet.userEmail, - userAndPet.userType, - organizationRoles, - GoogleProject(googleProjectId.value), - googleIamDAO, - googleStorageDAO - ) - val petRemoval = () => - removeFastPassGrants( - IamResourceTypes.Project, - googleProjectId.value, - userAndPet.petEmail, - IamMemberTypes.ServiceAccount, - organizationRoles, - GoogleProject(googleProjectId.value), - googleIamDAO, - googleStorageDAO - ) - - executeSerially(userRemoval, petRemoval) - } + ): Future[Unit] = + // note that IO traverse is serial which is good because parallel IAM updates will fight with each other + userAndPets.toSeq + .traverse { case (email, memberType) => + IO.fromFuture( + IO( + removeFastPassGrants( + IamResourceTypes.Project, + googleProjectId.value, + email, + memberType, + organizationRoles, + GoogleProject(googleProjectId.value), + googleIamDAO, + googleStorageDAO + ) + ) + ) + } + .void + .unsafeToFuture() private def removeFastPassBucketGrants(bucketName: String, - userAndPet: UserAndPetEmails, + userAndPets: UserAndPetEmails, organizationRoles: Set[String], googleProjectId: GoogleProjectId - ): Future[Unit] = { - - val userRemoval = () => - removeFastPassGrants( - IamResourceTypes.Bucket, - bucketName, - userAndPet.userEmail, - userAndPet.userType, - organizationRoles, - GoogleProject(googleProjectId.value), - googleIamDAO, - googleStorageDAO - ) - val petRemoval = () => - removeFastPassGrants( - IamResourceTypes.Bucket, - bucketName, - userAndPet.petEmail, - IamMemberTypes.ServiceAccount, - organizationRoles, - GoogleProject(googleProjectId.value), - googleIamDAO, - googleStorageDAO - ) - - executeSerially(userRemoval, petRemoval) - } - - private def executeSerially(futures: () => Future[Unit]*)(implicit executionContext: ExecutionContext): Future[Unit] = - futures.foldLeft(Future.successful[Unit](()))((a, b) => a.flatMap(_ => b())) + ): Future[Unit] = + // note that IO traverse is serial which is good because parallel IAM updates will fight with each other + userAndPets.toSeq + .traverse { case (email, memberType) => + IO.fromFuture( + IO( + removeFastPassGrants( + IamResourceTypes.Bucket, + bucketName, + email, + memberType, + organizationRoles, + GoogleProject(googleProjectId.value), + googleIamDAO, + googleStorageDAO + ) + ) + ) + } + .void + .unsafeToFuture() private def removeParentBucketReaderGrant(parentWorkspace: Workspace, samUserInfo: SamUserInfo)(implicit dataAccess: DataAccess diff --git a/core/src/main/scala/org/broadinstitute/dsde/rawls/jobexec/SubmissionMonitorActor.scala b/core/src/main/scala/org/broadinstitute/dsde/rawls/jobexec/SubmissionMonitorActor.scala index 0999f9ecd2..23853e62bc 100644 --- a/core/src/main/scala/org/broadinstitute/dsde/rawls/jobexec/SubmissionMonitorActor.scala +++ b/core/src/main/scala/org/broadinstitute/dsde/rawls/jobexec/SubmissionMonitorActor.scala @@ -275,10 +275,16 @@ trait SubmissionMonitor extends FutureSupport with LazyLogging with RawlsInstrum workspaceRec <- getWorkspace(dataAccess, submissionRec.workspaceId) } yield (wfRecs, submitter, workspaceRec) } flatMap { case (externalWorkflowIds, submitter, workspaceRec) => - for { - petUserInfo <- getPetServiceAccountUserInfo(GoogleProjectId(workspaceRec.googleProjectId), submitter) - workflowOutputs <- gatherWorkflowOutputs(externalWorkflowIds, petUserInfo) - } yield workflowOutputs + // if no running workflows, just return + if (externalWorkflowIds.isEmpty) { + Future.successful(Seq()) + } else { + // else, check Cromwell's status for each running workflow and get outputs for workflows completed in Cromwell + for { + petUserInfo <- getPetServiceAccountUserInfo(GoogleProjectId(workspaceRec.googleProjectId), submitter) + workflowOutputs <- gatherWorkflowOutputs(externalWorkflowIds, petUserInfo) + } yield workflowOutputs + } } map ExecutionServiceStatusResponse diff --git a/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceSpec.scala b/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceSpec.scala index fb854b5632..db211bc0fa 100644 --- a/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceSpec.scala +++ b/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/FastPassServiceSpec.scala @@ -515,7 +515,8 @@ class FastPassServiceSpec val userAccountFastPassGrants = userFastPassGrants.filter(_.accountType.equals(IamMemberTypes.User)) val petAccountFastPassGrants = userFastPassGrants.filter(_.accountType.equals(IamMemberTypes.ServiceAccount)) - userAccountFastPassGrants.length should be(petAccountFastPassGrants.length) + // there should be 2 pets for each user + userAccountFastPassGrants.length * 2 should be(petAccountFastPassGrants.length) val userResourceRoles = userAccountFastPassGrants.map(g => (g.resourceType, g.resourceName, g.organizationRole)).toSet @@ -533,7 +534,12 @@ class FastPassServiceSpec ), Duration.Inf ) + val defaultPetKey = Await.result( + services.fastPassMockSamDAO.getDefaultPetServiceAccountKeyForUser(services.ctx1), + Duration.Inf + ) val petEmail = FastPassServiceImpl.getEmailFromPetSaKey(petKey) + val defaultPetEmail = FastPassServiceImpl.getEmailFromPetSaKey(defaultPetKey) // The user is added to the project IAM policies with a condition verify(services.googleIamDAO).addRoles( @@ -555,6 +561,16 @@ class FastPassServiceSpec ArgumentMatchers.argThat((c: Option[Expr]) => c.exists(_.title.contains(userEmail.value))) ) + // The user's default pet is added to the project IAM policies with a condition + verify(services.googleIamDAO).addRoles( + ArgumentMatchers.eq(GoogleProject(testData.workspace.googleProjectId.value)), + ArgumentMatchers.eq(defaultPetEmail), + ArgumentMatchers.eq(IamMemberTypes.ServiceAccount), + ArgumentMatchers.eq(Set(services.terraWorkspaceCanComputeRole, services.terraWorkspaceNextflowRole)), + ArgumentMatchers.eq(false), + ArgumentMatchers.argThat((c: Option[Expr]) => c.exists(_.title.contains(userEmail.value))) + ) + // The user is added to the bucket IAM policies with a condition verify(services.googleStorageDAO).addIamRoles( ArgumentMatchers.eq(GcsBucketName(testData.workspace.bucketName)), @@ -576,6 +592,17 @@ class FastPassServiceSpec ArgumentMatchers.argThat((c: Option[Expr]) => c.exists(_.title.contains(userEmail.value))), ArgumentMatchers.eq(Some(GoogleProject(testData.workspace.googleProjectId.value))) ) + + // The user's default pet is added to the bucket IAM policies with a condition + verify(services.googleStorageDAO).addIamRoles( + ArgumentMatchers.eq(GcsBucketName(testData.workspace.bucketName)), + ArgumentMatchers.eq(defaultPetEmail), + ArgumentMatchers.eq(IamMemberTypes.ServiceAccount), + ArgumentMatchers.eq(Set(services.terraBucketWriterRole)), + ArgumentMatchers.eq(false), + ArgumentMatchers.argThat((c: Option[Expr]) => c.exists(_.title.contains(userEmail.value))), + ArgumentMatchers.eq(Some(GoogleProject(testData.workspace.googleProjectId.value))) + ) } it should "remove FastPass grants for a workspace" in withTestDataServices { services => @@ -605,7 +632,12 @@ class FastPassServiceSpec ), Duration.Inf ) + val defaultPetKey = Await.result( + services.fastPassMockSamDAO.getDefaultPetServiceAccountKeyForUser(services.ctx1), + Duration.Inf + ) val petEmail = FastPassServiceImpl.getEmailFromPetSaKey(petKey) + val defaultPetEmail = FastPassServiceImpl.getEmailFromPetSaKey(defaultPetKey) // The user is removed from the project IAM policies verify(services.googleIamDAO).removeRoles( @@ -625,6 +657,15 @@ class FastPassServiceSpec ArgumentMatchers.eq(false) ) + // The user's default pet is removed from the project IAM policies + verify(services.googleIamDAO).removeRoles( + ArgumentMatchers.eq(GoogleProject(testData.workspace.googleProjectId.value)), + ArgumentMatchers.eq(defaultPetEmail), + ArgumentMatchers.eq(IamMemberTypes.ServiceAccount), + ArgumentMatchers.eq(Set(services.terraWorkspaceCanComputeRole, services.terraWorkspaceNextflowRole)), + ArgumentMatchers.eq(false) + ) + // The user is removed from the bucket IAM policies verify(services.googleStorageDAO).removeIamRoles( ArgumentMatchers.eq(GcsBucketName(testData.workspace.bucketName)), @@ -644,6 +685,16 @@ class FastPassServiceSpec ArgumentMatchers.eq(false), ArgumentMatchers.eq(Some(GoogleProject(testData.workspace.googleProjectId.value))) ) + + // The user's default pet is removed from the bucket IAM policies + verify(services.googleStorageDAO).removeIamRoles( + ArgumentMatchers.eq(GcsBucketName(testData.workspace.bucketName)), + ArgumentMatchers.eq(defaultPetEmail), + ArgumentMatchers.eq(IamMemberTypes.ServiceAccount), + ArgumentMatchers.eq(Set(services.terraBucketWriterRole)), + ArgumentMatchers.eq(false), + ArgumentMatchers.eq(Some(GoogleProject(testData.workspace.googleProjectId.value))) + ) } it should "add FastPass grants for the user in the parent workspace bucket when a workspace is cloned" in withTestDataServices { @@ -686,12 +737,17 @@ class FastPassServiceSpec ), Duration.Inf ) + val defaultPetKey = Await.result( + services.fastPassMockSamDAO.getDefaultPetServiceAccountKeyForUser(services.ctx1), + Duration.Inf + ) val childWorkspacePetEmail = FastPassServiceImpl.getEmailFromPetSaKey(childWorkspacePetKey) + val defaultPetEmail = FastPassServiceImpl.getEmailFromPetSaKey(defaultPetKey) parentWorkspacePetEmail should not be childWorkspacePetEmail parentWorkspaceFastPassGrantsAfter.map(_.accountEmail).toSet should be( - Set(childWorkspacePetEmail, WorkbenchEmail(samUserStatus.userEmail)) + Set(childWorkspacePetEmail, defaultPetEmail, WorkbenchEmail(samUserStatus.userEmail)) ) } @@ -751,7 +807,8 @@ class FastPassServiceSpec userWriterGrants.map(_.organizationRole) should contain only (writerCanComputeRoles: _*) userWriterGrants.map(_.accountEmail.value).toSet should contain only ( testData.userWriter.userEmail.value, - MockFastPassService.buildPetEmail(testData.userWriter, testData.workspace.googleProjectId.value).value + MockFastPassService.buildPetEmail(testData.userWriter, testData.workspace.googleProjectId.value).value, + MockFastPassService.buildPetEmail(testData.userWriter, MockFastPassService.defaultPetGoogleProject).value ) userReaderGrants.map(_.organizationRole) should contain only (readerRoles: _*) userReaderGrants.map(_.accountEmail.value).toSet should contain only ( @@ -960,11 +1017,16 @@ class FastPassServiceSpec ), Duration.Inf ) + val defaultPetKey = Await.result( + services.fastPassMockSamDAO.getDefaultPetServiceAccountKeyForUser(services.ctx1), + Duration.Inf + ) + val defaultPetEmail = FastPassServiceImpl.getEmailFromPetSaKey(defaultPetKey) val petEmail = FastPassServiceImpl.getEmailFromPetSaKey(petKey) workspaceFastPassGrants should not be empty workspaceFastPassGrants.map(_.accountType) should contain only (IamMemberTypes.ServiceAccount) - workspaceFastPassGrants.map(_.accountEmail) should contain only (userEmail, petEmail) + workspaceFastPassGrants.map(_.accountEmail) should contain only (userEmail, petEmail, defaultPetEmail) // The user is added to the project IAM policies with a condition verify(services.googleIamDAO).addRoles( diff --git a/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/MockFastPassService.scala b/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/MockFastPassService.scala index 38063a9f87..0d94c18bf9 100644 --- a/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/MockFastPassService.scala +++ b/core/src/test/scala/org/broadinstitute/dsde/rawls/fastpass/MockFastPassService.scala @@ -138,6 +138,14 @@ object MockFastPassService { ArgumentMatchers.eq(testUser.userEmail) ) + doAnswer { _ => + Future.successful(projectPetInfo(defaultPetGoogleProject)._2) + } + .when(samDAO) + .getDefaultPetServiceAccountKeyForUser( + ArgumentMatchers.any[RawlsRequestContext] + ) + doAnswer { invocation => val googleProjectId = invocation.getArgument[GoogleProjectId](1) Future.successful(projectPetInfo(googleProjectId.value)._1) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ba01e14e26..64313aea54 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -79,7 +79,7 @@ object Dependencies { val antlrParser: ModuleID = "org.antlr" % "antlr4-runtime" % "4.13.2" // protobuf is only need to use the MySQL X DevAPI which we don't. exclude it to avoid interference with Google client libraries val mysqlConnector: ModuleID = "com.mysql" % "mysql-connector-j" % "9.1.0" exclude("com.google.protobuf", "protobuf-java") - val liquibaseCore: ModuleID = "org.liquibase" % "liquibase-core" % "4.29.2" + val liquibaseCore: ModuleID = "org.liquibase" % "liquibase-core" % "4.30.0" val jakartaWsRs: ModuleID = "jakarta.ws.rs" % "jakarta.ws.rs-api" % "4.0.0" val jerseyJnhConnector: ModuleID = "org.glassfish.jersey.connectors" % "jersey-jnh-connector" % "3.1.9" @@ -132,7 +132,7 @@ object Dependencies { val resourceBufferService = clientLibExclusions("bio.terra" % "terra-resource-buffer-client" % "0.198.42-SNAPSHOT") val billingProfileManager = clientLibExclusions("bio.terra" % "billing-profile-manager-client" % "0.1.589-SNAPSHOT") val terraCommonLib = tclExclusions(clientLibExclusions("bio.terra" % "terra-common-lib" % "0.1.23-SNAPSHOT" classifier "plain")) - val sam: ModuleID = clientLibExclusions("org.broadinstitute.dsde.workbench" %% "sam-client" % "v0.0.306") + val sam: ModuleID = clientLibExclusions("org.broadinstitute.dsde.workbench" %% "sam-client" % "v0.0.319") val leonardo: ModuleID = "org.broadinstitute.dsde.workbench" % "leonardo-client_2.13" % "1.3.6-2e87300" // OpenTelemetry