diff --git a/automation/project/Dependencies.scala b/automation/project/Dependencies.scala index 48f2250710..04b0361bd5 100644 --- a/automation/project/Dependencies.scala +++ b/automation/project/Dependencies.scala @@ -7,7 +7,7 @@ object Dependencies { val akkaV = "2.5.7" val akkaHttpV = "10.0.10" - val workbenchModelV = "0.10-6800f3a" + val workbenchModelV = "0.11-7657779-SNAP" val workbenchGoogleV = "0.16-847c3ff" val workbenchServiceTestV = "0.10-61981d5" diff --git a/docker/stand-alone/sam.conf b/docker/stand-alone/sam.conf index bc2c20ded6..18b1ddcff5 100644 --- a/docker/stand-alone/sam.conf +++ b/docker/stand-alone/sam.conf @@ -16,7 +16,7 @@ schemaLock { recheckTimeInterval = 5 maxTimeToWait = 60 instanceId = instance1 - schemaVersion = 1 + schemaVersion = 2 } emailDomain = ${EMAIL_DOMAIN} \ No newline at end of file diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9719771856..a985b9a690 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val scalaTestV = "3.0.1" val workbenchUtilV = "0.2-d6801ce" - val workbenchModelV = "0.10-8e9ac2a" + val workbenchModelV = "0.11-7657779-SNAP" val workbenchGoogleV = "0.16-f339f30" val workbenchNotificationsV = "0.1-21c2127" diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutes.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutes.scala index 6cf5821a58..f4e843f7fc 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutes.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutes.scala @@ -24,7 +24,8 @@ trait ResourceRoutes extends UserInfoDirectives with SecurityDirectives with Sam def withResourceType(name: ResourceTypeName): Directive1[ResourceType] = { onSuccess(resourceService.getResourceType(name)).map { - case Some(resourceType) => resourceType + case Some(resourceType) => + resourceType case None => throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.NotFound, s"resource type ${name.value} not found")) } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/StandardUserInfoDirectives.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/StandardUserInfoDirectives.scala index 07c2efd209..c88e3eacb5 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/StandardUserInfoDirectives.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/StandardUserInfoDirectives.scala @@ -1,13 +1,13 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api +import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.headers.OAuth2BearerToken +import akka.http.scaladsl.server.Directives.{headerValueByName, onSuccess, optionalHeaderValueByName} import akka.http.scaladsl.server._ -import akka.http.scaladsl.server.Directives.{headerValueByName, onSuccess} -import org.broadinstitute.dsde.workbench.model._ -import akka.http.scaladsl.server.Directives.headerValueByName import org.broadinstitute.dsde.workbench.model.google.ServiceAccountSubjectId -import org.broadinstitute.dsde.workbench.model.{WorkbenchEmail, WorkbenchUserId} - +import org.broadinstitute.dsde.workbench.model.{WorkbenchEmail, _} +import akka.http.scaladsl.server.directives.OnSuccessMagnet._ import scala.concurrent.{ExecutionContext, Future} @@ -17,30 +17,59 @@ trait StandardUserInfoDirectives extends UserInfoDirectives { val petSAdomain = "\\S+@\\S+\\.iam\\.gserviceaccount\\.com".r - private def isPetSA(email: WorkbenchEmail) = { + def isPetSA(email: WorkbenchEmail) = { petSAdomain.pattern.matcher(email.value).matches } def requireUserInfo: Directive1[UserInfo] = ( - headerValueByName("OIDC_access_token") & - headerValueByName("OIDC_CLAIM_user_id") & - headerValueByName("OIDC_CLAIM_expires_in") & - headerValueByName("OIDC_CLAIM_email") + headerValueByName(accessTokenHeader) & +// headerValueByName(userIdHeader) & TODO: add this back once once https://broadinstitute.atlassian.net/browse/GAWB-3748 is done + headerValueByName(expiresInHeader) & + headerValueByName(emailHeader) & + optionalHeaderValueByName(googleSubjectIdHeader) //it's required for PET accounts, but optional for regular users. + // TODO: until https://broadinstitute.atlassian.net/browse/GAWB-3748 is done, googleSubjectIdHeader is required ) tflatMap { - case (token, userId, expiresIn, email) => { - val userInfo = UserInfo(OAuth2BearerToken(token), WorkbenchUserId(userId), WorkbenchEmail(email), expiresIn.toLong) - onSuccess(getUserFromPetServiceAccount(userInfo).map { - case Some(petOwnerUser) => UserInfo(OAuth2BearerToken(token), petOwnerUser.id, petOwnerUser.email, expiresIn.toLong) - case None => userInfo - }) + case (token, expiresIn, email, googleSubjectId) => { + val em = WorkbenchEmail(email) + val gSid = googleSubjectId.map(GoogleSubjectId) + onSuccess{ + val res: Future[UserInfo] = if (isPetSA(em)) { + // If it's a PET account, we treat it as its owner + googleSubjectId.fold[Future[UserInfo]](Future.failed(new WorkbenchException("google subject Id is required for PET account"))){ + gid => + directoryDAO.getUserFromPetServiceAccount(ServiceAccountSubjectId(gid)).flatMap(_.fold[Future[UserInfo]](Future.failed(new WorkbenchException("no owner found for this PET account")))(pet => Future.successful(UserInfo(OAuth2BearerToken(token), pet.id, gSid, pet.email, expiresIn.toLong)))) + } + } else { + for{ + subject <- directoryDAO.loadSubjectFromGoogleSubjectId(GoogleSubjectId(googleSubjectId.get)) //dirty temporary code TODO: this call should be removed after https://broadinstitute.atlassian.net/browse/GAWB-3747 + userInfo <- subject match{ + case Some(uid: WorkbenchUserId) => Future.successful(UserInfo(OAuth2BearerToken(token), uid, gSid, WorkbenchEmail(email), expiresIn.toLong)) + case Some(_) => Future.failed(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Conflict, s"subjectId $googleSubjectId is not a WorkbenchUser"))) + case None => Future.failed(new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.NotFound, s"user $googleSubjectId not found"))) + } + }yield userInfo + } + + res + } } } - private def getUserFromPetServiceAccount(userInfo: UserInfo):Future[Option[WorkbenchUser]] = { - if (isPetSA(userInfo.userEmail)) { - directoryDAO.getUserFromPetServiceAccount(ServiceAccountSubjectId(userInfo.userId.value)) - } else { - Future.successful(None) + def requireCreateUser: Directive1[CreateWorkbenchUserAPI] = ( + headerValueByName(googleSubjectIdHeader) & + headerValueByName(emailHeader) + ) tflatMap { + case (googleSubjectId, email) => { + val em = WorkbenchEmail(email) + onSuccess{ + val res: Future[CreateWorkbenchUserAPI] = if (isPetSA(em)) { + // If it's a PET account, we don't creat a user for it. Its owner should already exist in ldap + Future.failed[CreateWorkbenchUserAPI](new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Conflict, s"owner for PET ${googleSubjectId} already exists"))) + } else { + Future.successful(CreateWorkbenchUserAPI(genWorkbenchUserId(System.currentTimeMillis()), GoogleSubjectId(googleSubjectId), WorkbenchEmail(email))) + } + res + } } } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserInfoDirectives.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserInfoDirectives.scala index 6c7b64efec..8b0e4b7f83 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserInfoDirectives.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserInfoDirectives.scala @@ -3,7 +3,7 @@ package org.broadinstitute.dsde.workbench.sam.api import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.Directives.onSuccess import akka.http.scaladsl.server.{Directive0, Directive1, Directives} -import org.broadinstitute.dsde.workbench.model.{ErrorReport, UserInfo, WorkbenchExceptionWithErrorReport} +import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.sam.service.CloudExtensions import org.broadinstitute.dsde.workbench.sam._ import org.broadinstitute.dsde.workbench.sam.directory.DirectoryDAO @@ -17,6 +17,8 @@ trait UserInfoDirectives { def requireUserInfo: Directive1[UserInfo] + def requireCreateUser: Directive1[CreateWorkbenchUserAPI] + def asWorkbenchAdmin(userInfo: UserInfo): Directive0 = { Directives.mapInnerRoute { r => onSuccess(cloudExtensions.isWorkbenchAdmin(userInfo.userEmail)) { isAdmin => diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutes.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutes.scala index 7f2c86cf02..87253ccb1a 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutes.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutes.scala @@ -1,11 +1,12 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server import akka.http.scaladsl.server.Directives._ -import org.broadinstitute.dsde.workbench.model.{WorkbenchEmail, WorkbenchUser, WorkbenchUserId} import org.broadinstitute.dsde.workbench.model.google.GoogleProject +import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import org.broadinstitute.dsde.workbench.sam.service.UserService @@ -20,61 +21,64 @@ trait UserRoutes extends UserInfoDirectives { def userRoutes: server.Route = pathPrefix("user") { - requireUserInfo { userInfo => - (pathPrefix("v1") | pathEndOrSingleSlash){ + (pathPrefix("v1") | pathEndOrSingleSlash) { pathEndOrSingleSlash { post { - complete { - userService.createUser(WorkbenchUser(userInfo.userId, userInfo.userEmail)).map(userStatus => StatusCodes.Created -> userStatus) - } - } ~ - get { - parameter("userDetailsOnly".?) { userDetailsOnly => - complete { - userService.getUserStatus(userInfo.userId, userDetailsOnly.exists(_.equalsIgnoreCase("true"))).map { statusOption => - statusOption.map { status => - StatusCodes.OK -> Option(status) - }.getOrElse(StatusCodes.NotFound -> None) - } - } - } - } - } - } ~ - pathPrefix("v2") { - pathPrefix("self") { - pathEndOrSingleSlash { - post { + requireCreateUser { createUser => complete { - userService.createUser(WorkbenchUser(userInfo.userId, userInfo.userEmail)).map(userStatus => StatusCodes.Created -> userStatus) + userService.createUser(createUser).map(userStatus => StatusCodes.Created -> userStatus) } } - } ~ - path("info") { + } ~ requireUserInfo { user => get { - complete { - userService.getUserStatusInfo(userInfo.userId).map { statusOption => - statusOption.map { status => - StatusCodes.OK -> Option(status) - }.getOrElse(StatusCodes.NotFound -> None) + parameter("userDetailsOnly".?) { userDetailsOnly => + complete { + userService.getUserStatus(user.userId, userDetailsOnly.exists(_.equalsIgnoreCase("true"))).map { statusOption => + statusOption.map { status => + StatusCodes.OK -> Option(status) + }.getOrElse(StatusCodes.NotFound -> None) + } } } } - } ~ - path("diagnostics") { - get { - complete { - userService.getUserStatusDiagnostics(userInfo.userId).map { statusOption => - statusOption.map { status => - StatusCodes.OK -> Option(status) - }.getOrElse(StatusCodes.NotFound -> None) + } + } + } ~ pathPrefix("v2") { + pathPrefix("self") { + pathEndOrSingleSlash { + post { + requireCreateUser { createUser => + complete { + userService.createUser(createUser).map(userStatus => StatusCodes.Created -> userStatus) + } + } } + }~ requireUserInfo {user => + path("info") { + get { + complete { + userService.getUserStatusInfo(user.userId).map { statusOption => + statusOption.map { status => + StatusCodes.OK -> Option(status) + }.getOrElse(StatusCodes.NotFound -> None) + } + } + } + } ~ + path("diagnostics") { + get { + complete { + userService.getUserStatusDiagnostics(user.userId).map { statusOption => + statusOption.map { status => + StatusCodes.OK -> Option(status) + }.getOrElse(StatusCodes.NotFound -> None) + } + } + } + } } - } } } - } - } } def adminUserRoutes: server.Route = @@ -149,3 +153,4 @@ trait UserRoutes extends UserInfoDirectives { } } } + diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/package.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/package.scala new file mode 100644 index 0000000000..b3312fcc13 --- /dev/null +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/api/package.scala @@ -0,0 +1,9 @@ +package org.broadinstitute.dsde.workbench.sam + +package object api { + val googleSubjectIdHeader = "OIDC_CLAIM_google_subject_id" + val accessTokenHeader = "OIDC_access_token" + val expiresInHeader = "OIDC_CLAIM_expires_in" + val emailHeader = "OIDC_CLAIM_email" + val userIdHeader = "OIDC_CLAIM_user_id" +} diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/DirectoryDAO.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/DirectoryDAO.scala index c6ea675cbe..22f5fa196a 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/DirectoryDAO.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/DirectoryDAO.scala @@ -35,6 +35,7 @@ trait DirectoryDAO { def loadSubjectFromEmail(email: WorkbenchEmail): Future[Option[WorkbenchSubject]] def loadSubjectEmail(subject: WorkbenchSubject): Future[Option[WorkbenchEmail]] def loadSubjectEmails(subjects: Set[WorkbenchSubject]): Future[Set[WorkbenchEmail]] + def loadSubjectFromGoogleSubjectId(googleSubjectId: GoogleSubjectId): Future[Option[WorkbenchSubject]] def createUser(user: WorkbenchUser): Future[WorkbenchUser] def loadUser(userId: WorkbenchUserId): Future[Option[WorkbenchUser]] @@ -57,4 +58,5 @@ trait DirectoryDAO { def deletePetServiceAccount(petServiceAccountId: PetServiceAccountId): Future[Unit] def getAllPetServiceAccountsForUser(userId: WorkbenchUserId): Future[Seq[PetServiceAccount]] def updatePetServiceAccount(petServiceAccount: PetServiceAccount): Future[PetServiceAccount] + def updateGoogleSubjectId(user: WorkbenchUser): Future[WorkbenchUser] } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAO.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAO.scala index 433fb99f29..3a62decc17 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAO.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAO.scala @@ -151,13 +151,23 @@ class LdapDirectoryDAO(protected val ldapConnectionPool: LDAPConnectionPool, pro } override def createUser(user: WorkbenchUser): Future[WorkbenchUser] = Future { - ldapConnectionPool.add(userDn(user.id), + user.googleSubjectId.fold(ldapConnectionPool.add(userDn(user.id), new Attribute(Attr.email, user.email.value), new Attribute(Attr.sn, user.id.value), new Attribute(Attr.cn, user.id.value), new Attribute(Attr.uid, user.id.value), new Attribute("objectclass", Seq("top", "workbenchPerson").asJava) - ) + )){ + googleSubjectId => + ldapConnectionPool.add(userDn(user.id), + new Attribute(Attr.email, user.email.value), + new Attribute(Attr.sn, user.id.value), + new Attribute(Attr.cn, user.id.value), + new Attribute(Attr.uid, user.id.value), + new Attribute(Attr.googleSubjectId, googleSubjectId.value), + new Attribute("objectclass", Seq("top", "workbenchPerson").asJava) + ) + } user }.recover { @@ -179,7 +189,7 @@ class LdapDirectoryDAO(protected val ldapConnectionPool: LDAPConnectionPool, pro val uid = getAttribute(results, Attr.uid).getOrElse(throw new WorkbenchException(s"${Attr.uid} attribute missing")) val email = getAttribute(results, Attr.email).getOrElse(throw new WorkbenchException(s"${Attr.email} attribute missing")) - WorkbenchUser(WorkbenchUserId(uid), WorkbenchEmail(email)) + WorkbenchUser(WorkbenchUserId(uid), getAttribute(results, Attr.googleSubjectId).map(GoogleSubjectId), WorkbenchEmail(email)) } override def loadUsers(userIds: Set[WorkbenchUserId]): Future[Seq[WorkbenchUser]] = Future { @@ -327,4 +337,19 @@ class LdapDirectoryDAO(protected val ldapConnectionPool: LDAPConnectionPool, pro ldapConnectionPool.modify(petDn(petServiceAccount.id), modifications.asJava) petServiceAccount } + + override def loadSubjectFromGoogleSubjectId(googleSubjectId: GoogleSubjectId): Future[Option[WorkbenchSubject]] = Future { + ldapConnectionPool.search(directoryConfig.baseDn, SearchScope.SUB, Filter.createEqualityFilter(Attr.googleSubjectId, googleSubjectId.value)).getSearchEntries.asScala match { + case Seq() => None + case Seq(subject) => Option(dnToSubject(subject.getDN)) + case subjects => throw new WorkbenchException(s"Database error: googleSubjectId $googleSubjectId refers to too many subjects: ${subjects.map(_.getDN)}") + } + } + + override def updateGoogleSubjectId(user: WorkbenchUser): Future[WorkbenchUser] = + user.googleSubjectId match { + case None => Future.successful(user) //No op + case Some(sid) => + Future{ldapConnectionPool.modify(userDn(user.id), new Modification(ModificationType.REPLACE, Attr.googleSubjectId, sid.value)); user} + } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutes.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutes.scala index 5455e4f4de..10ec339f04 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutes.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutes.scala @@ -32,7 +32,8 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with googleExtensions.getPetServiceAccountKey(WorkbenchEmail(userEmail), GoogleProject(project)) map { // parse json to ensure it is json and tells akka http the right content-type case Some(key) => StatusCodes.OK -> key.parseJson - case None => throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.NotFound, "pet service account not found")) + case None => + throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.NotFound, "pet service account not found")) } } } @@ -44,7 +45,7 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with get { complete { import spray.json._ - googleExtensions.getArbitraryPetServiceAccountKey(WorkbenchUser(userInfo.userId, userInfo.userEmail)).map(key => StatusCodes.OK -> key.parseJson) + googleExtensions.getArbitraryPetServiceAccountKey(WorkbenchUser(userInfo.userId, None, userInfo.userEmail)).map(key => StatusCodes.OK -> key.parseJson) } } } @@ -54,7 +55,7 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with post { entity(as[Set[String]]) { scopes => complete { - googleExtensions.getArbitraryPetServiceAccountToken(WorkbenchUser(userInfo.userId, userInfo.userEmail), scopes).map { token => + googleExtensions.getArbitraryPetServiceAccountToken(WorkbenchUser(userInfo.userId, None, userInfo.userEmail), scopes).map { token => StatusCodes.OK -> JsString(token) } } @@ -68,7 +69,7 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with complete { import spray.json._ // parse json to ensure it is json and tells akka http the right content-type - googleExtensions.getPetServiceAccountKey(WorkbenchUser(userInfo.userId, userInfo.userEmail), GoogleProject(project)).map(key => StatusCodes.OK -> key.parseJson) + googleExtensions.getPetServiceAccountKey(WorkbenchUser(userInfo.userId, None, userInfo.userEmail), GoogleProject(project)).map(key => StatusCodes.OK -> key.parseJson) } } ~ path(Segment) { keyId => @@ -83,7 +84,7 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with post { entity(as[Set[String]]) { scopes => complete { - googleExtensions.getPetServiceAccountToken(WorkbenchUser(userInfo.userId, userInfo.userEmail), GoogleProject(project), scopes).map { token => + googleExtensions.getPetServiceAccountToken(WorkbenchUser(userInfo.userId, None, userInfo.userEmail), GoogleProject(project), scopes).map { token => StatusCodes.OK -> JsString(token) } } @@ -93,7 +94,7 @@ trait GoogleExtensionRoutes extends ExtensionRoutes with UserInfoDirectives with pathEnd { get { complete { - googleExtensions.createUserPetServiceAccount(WorkbenchUser(userInfo.userId, userInfo.userEmail), GoogleProject(project)).map { petSA => + googleExtensions.createUserPetServiceAccount(WorkbenchUser(userInfo.userId, None, userInfo.userEmail), GoogleProject(project)).map { petSA => StatusCodes.OK -> petSA.serviceAccount.email } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensions.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensions.scala index 1179484b86..536098f21a 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensions.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensions.scala @@ -10,6 +10,7 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException import com.google.auth.oauth2.ServiceAccountCredentials import com.typesafe.scalalogging.LazyLogging import io.grpc.Status.Code +import org.broadinstitute.dsde.workbench import org.broadinstitute.dsde.workbench.dataaccess.NotificationDAO import org.broadinstitute.dsde.workbench.google.{GoogleDirectoryDAO, GoogleIamDAO, GoogleProjectDAO, GooglePubSubDAO, GoogleStorageDAO} import org.broadinstitute.dsde.workbench.model.Notifications.Notification @@ -40,14 +41,14 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce private val maxGroupEmailLength = 64 - private[google] def toProxyFromUser(user: WorkbenchUser): WorkbenchEmail = { + private[google] def toProxyFromUser(userId: WorkbenchUserId): WorkbenchEmail = { /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 val username = user.email.value.split("@").head val emailSuffix = s"_${user.id.value}@${googleServicesConfig.appsDomain}" val maxUsernameLength = maxGroupEmailLength - emailSuffix.length WorkbenchEmail(username.take(maxUsernameLength) + emailSuffix) */ - WorkbenchEmail(s"${googleServicesConfig.resourceNamePrefix.getOrElse("")}PROXY_${user.id.value}@${googleServicesConfig.appsDomain}") + WorkbenchEmail(s"${googleServicesConfig.resourceNamePrefix.getOrElse("")}PROXY_${userId.value}@${googleServicesConfig.appsDomain}") /**/ } @@ -85,9 +86,17 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce )) - val serviceAccountUserInfo = UserInfo(OAuth2BearerToken(""), WorkbenchUserId(googleServicesConfig.serviceAccountClientId), googleServicesConfig.serviceAccountClientEmail, 0) + val ownerGoogleSubjectId = GoogleSubjectId(googleServicesConfig.serviceAccountClientId) for { - _ <- samApplication.userService.createUser(WorkbenchUser(serviceAccountUserInfo.userId, serviceAccountUserInfo.userEmail)) recover { + user <- directoryDAO.loadSubjectFromGoogleSubjectId(ownerGoogleSubjectId) + + subject <- directoryDAO.loadSubjectFromGoogleSubjectId(GoogleSubjectId(googleServicesConfig.serviceAccountClientId)) //dirty temporary code TODO: this call should be removed after https://broadinstitute.atlassian.net/browse/GAWB-3747 + serviceAccountUserInfo = subject match{ + case Some(uid: WorkbenchUserId) => UserInfo(OAuth2BearerToken(""), uid, Some(ownerGoogleSubjectId), googleServicesConfig.serviceAccountClientEmail, 0) + case Some(_) => throw new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Conflict, s"subjectId in configration ${googleServicesConfig.serviceAccountClientId} is not a valid user")) + case None => UserInfo(OAuth2BearerToken(""), genWorkbenchUserId(System.currentTimeMillis()), Some(ownerGoogleSubjectId), googleServicesConfig.serviceAccountClientEmail, 0) + } + _ <- samApplication.userService.createUser(workbench.model.CreateWorkbenchUserAPI(serviceAccountUserInfo.userId, GoogleSubjectId(googleServicesConfig.serviceAccountClientId), serviceAccountUserInfo.userEmail)) recover { case e: WorkbenchExceptionWithErrorReport if e.errorReport.statusCode == Option(StatusCodes.Conflict) => } @@ -96,7 +105,6 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce _ <- samApplication.resourceService.createResource(extensionResourceType, GoogleExtensions.resourceId, serviceAccountUserInfo) recover { case e: WorkbenchExceptionWithErrorReport if e.errorReport.statusCode == Option(StatusCodes.Conflict) => } - _ <- googleKeyCache.onBoot() } yield () } @@ -121,15 +129,13 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce } override def onUserCreate(user: WorkbenchUser): Future[Unit] = { - val proxyEmail = toProxyFromUser(user) + val proxyEmail = toProxyFromUser(user.id) for { _ <- googleDirectoryDAO.createGroup(user.email.value, proxyEmail) recover { case e:GoogleJsonResponseException if e.getDetails.getCode == StatusCodes.Conflict.intValue => () } _ <- googleDirectoryDAO.addMemberToGroup(proxyEmail, WorkbenchEmail(user.email.value)) - allUsersGroup <- getOrCreateAllUsersGroup(directoryDAO) - _ <- googleDirectoryDAO.addMemberToGroup(allUsersGroup.email, proxyEmail) /* Re-enable this code after fixing rawls for GAWB-2933 @@ -217,13 +223,12 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce // SA does not exist in google, create it and add it to the proxy group case None => for { sa <- googleIamDAO.createServiceAccount(project, petSaName, petSaDisplayName) - _ <- withProxyEmail(user.id) { proxyEmail => googleDirectoryDAO.addMemberToGroup(proxyEmail, sa.email) } + r <- withProxyEmail(user.id){proxyEmail => googleDirectoryDAO.addMemberToGroup(proxyEmail, sa.email)} } yield sa // SA already exists in google, use it case Some(sa) => Future.successful(sa) } - pet <- (maybePet, maybeServiceAccount) match { // pet does not exist in ldap, create it and enable the identity case (None, _) => for { @@ -237,7 +242,6 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce // everything already existed case (Some(p), Some(_)) => Future.successful(p) } - } yield pet // In the case of high concurrency, the above code may find that there is no pet SA and then try to create one, getting @@ -256,7 +260,7 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce for { subject <- directoryDAO.loadSubjectFromEmail(userEmail) key <- subject match { - case Some(userId: WorkbenchUserId) => getPetServiceAccountKey(WorkbenchUser(userId, userEmail), project).map(Option(_)) + case Some(userId: WorkbenchUserId) => getPetServiceAccountKey(WorkbenchUser(userId, None, userEmail), project).map(Option(_)) case _ => Future.successful(None) } } yield key @@ -475,7 +479,7 @@ class GoogleExtensions(val directoryDAO: DirectoryDAO, val accessPolicyDAO: Acce /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 directoryDAO.readProxyGroup(userId) */ - Future.successful(Some(toProxyFromUser(WorkbenchUser(userId, null)))) + Future.successful(Some(toProxyFromUser(userId))) } private def withProxyEmail[T](userId: WorkbenchUserId)(f: WorkbenchEmail => Future[T]): Future[T] = { diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/openam/LdapAccessPolicyDAO.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/openam/LdapAccessPolicyDAO.scala index e58aeb4d8b..4238e1f1b0 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/openam/LdapAccessPolicyDAO.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/openam/LdapAccessPolicyDAO.scala @@ -133,6 +133,6 @@ class LdapAccessPolicyDAO(protected val ldapConnectionPool: LDAPConnectionPool, val uid = getAttribute(entry, Attr.uid).getOrElse(throw new WorkbenchException(s"${Attr.uid} attribute missing")) val email = getAttribute(entry, Attr.email).getOrElse(throw new WorkbenchException(s"${Attr.email} attribute missing")) - WorkbenchUser(WorkbenchUserId(uid), WorkbenchEmail(email)) + WorkbenchUser(WorkbenchUserId(uid), getAttribute(entry, Attr.googleSubjectId).map(GoogleSubjectId), WorkbenchEmail(email)) } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/package.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/package.scala index 9dc98bcf20..f781e9c3d4 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/package.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/package.scala @@ -1,10 +1,33 @@ package org.broadinstitute.dsde.workbench -import org.broadinstitute.dsde.workbench.model.ErrorReportSource +import java.security.SecureRandom + +import org.apache.commons.codec.binary.Hex +import org.broadinstitute.dsde.workbench.model.{ErrorReportSource, WorkbenchUserId} /** * Created by dvoet on 5/18/17. */ package object sam { implicit val errorReportSource = ErrorReportSource("sam") + + val random = SecureRandom.getInstance("NativePRNGNonBlocking") + + // Generate a 21 digits unique identifier. First char is fixed 2 + // CurrentMillis.append(randomString) + private[workbench] def genRandom(currentMilli: Long): String = { + val currentMillisString = currentMilli.toString + // one hexdecimal is 4 bits, one byte can generate 2 hexdecial number, so we only need half the number of bytes, which is 8 + // currentMilli is 13 digits, and it'll be another 200 years before it becomes 14 digits. So we're assuming currentMillis is 13 digits here + val bytes = new Array[Byte](4) + random.nextBytes(bytes) + val r = new String(Hex.encodeHex(bytes)) + // since googleSubjectId starts with 1, we are replacing 1 with 2 to avoid conflicts with existing uid + val front = if(currentMillisString(0) == '1') currentMillisString.replaceFirst("1", "2") else currentMilli + front + r + } + + def genWorkbenchUserId(currentMilli: Long): WorkbenchUserId = { + WorkbenchUserId(genRandom(currentMilli)) + } } diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/schema/JndiSchemaDAO.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/schema/JndiSchemaDAO.scala index e41bf74237..f515bcb7f3 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/schema/JndiSchemaDAO.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/schema/JndiSchemaDAO.scala @@ -36,6 +36,7 @@ object JndiSchemaDAO { val givenName = "givenName" val sn = "sn" val uid = "uid" + val googleSubjectId = "googleSubjectId" val project = "project" val proxyEmail = "proxyEmail" } @@ -242,6 +243,7 @@ class JndiSchemaDAO(protected val directoryConfig: DirectoryConfig, val schemaLo val schema = ctx.getSchema("") createAttributeDefinition(schema, "1.3.6.1.4.1.18060.0.4.3.2.30", Attr.proxyEmail, "proxy group email", true) + createAttributeDefinition(schema, "1.3.6.1.4.1.18060.0.4.3.2.34", Attr.googleSubjectId, "google subject Id", true) val attrs = new BasicAttributes(true) // Ignore case attrs.put("NUMERICOID", "1.3.6.1.4.1.18060.0.4.3.2.300") @@ -256,6 +258,8 @@ class JndiSchemaDAO(protected val directoryConfig: DirectoryConfig, val schemaLo val may = new BasicAttribute("MAY") may.add(Attr.proxyEmail) + may.add(Attr.googleSubjectId) + attrs.put(may) // Add the new schema object for "fooObjectClass" @@ -294,6 +298,7 @@ class JndiSchemaDAO(protected val directoryConfig: DirectoryConfig, val schemaLo // Intentionally ignores errors Try { schema.destroySubcontext("ClassDefinition/" + ObjectClass.workbenchPerson) } Try { schema.destroySubcontext("AttributeDefinition/" + Attr.proxyEmail) } + Try { schema.destroySubcontext("AttributeDefinition/" + Attr.googleSubjectId) } } private def removeWorkbenchGroupSchema(): Future[Unit] = withContext { ctx => diff --git a/src/main/scala/org/broadinstitute/dsde/workbench/sam/service/UserService.scala b/src/main/scala/org/broadinstitute/dsde/workbench/sam/service/UserService.scala index e05ebed3a5..880e17241c 100644 --- a/src/main/scala/org/broadinstitute/dsde/workbench/sam/service/UserService.scala +++ b/src/main/scala/org/broadinstitute/dsde/workbench/sam/service/UserService.scala @@ -1,7 +1,8 @@ -package org.broadinstitute.dsde.workbench.sam.service +package org.broadinstitute.dsde.workbench.sam +package service +import akka.http.scaladsl.model.StatusCodes import javax.naming.NameNotFoundException - import com.typesafe.scalalogging.LazyLogging import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.sam.directory.DirectoryDAO @@ -14,10 +15,10 @@ import scala.concurrent.{ExecutionContext, Future} */ class UserService(val directoryDAO: DirectoryDAO, val cloudExtensions: CloudExtensions)(implicit val executionContext: ExecutionContext) extends LazyLogging { - def createUser(user: WorkbenchUser): Future[UserStatus] = { + def createUser(user: CreateWorkbenchUserAPI): Future[UserStatus] = { for { allUsersGroup <- cloudExtensions.getOrCreateAllUsersGroup(directoryDAO) - createdUser <- directoryDAO.createUser(user) + createdUser <- registerUser(user) _ <- directoryDAO.enableIdentity(user.id) _ <- directoryDAO.addGroupMember(allUsersGroup.id, user.id) _ <- cloudExtensions.onUserCreate(createdUser) @@ -27,6 +28,34 @@ class UserService(val directoryDAO: DirectoryDAO, val cloudExtensions: CloudExte } } + /** + * If googleSubjectId exists in ldap, return 409; else if email also exists, we lookup pre-created user record and update + * its googleSubjectId field; otherwise, we create a new user + * + * GoogleSubjectId Email + * no no ---> We've never seen this user before, create a new user + * no no ---> Someone invited this user previous and we have a record for this user already. We just need to update GoogleSubjetId field for this user. + * yes skip ---> User exists. Do nothing. + * yes skip ---> User exists. Do nothing. + */ + private def registerUser(user: CreateWorkbenchUserAPI): Future[WorkbenchUser] = for{ + existingSubject <- directoryDAO.loadSubjectFromGoogleSubjectId(user.googleSubjectId) + user <- existingSubject.fold[Future[WorkbenchUser]]{ + for{ + subject <- directoryDAO.loadSubjectFromEmail(user.email) + updated <- subject match{ + case Some(uid : WorkbenchUserId) => + val toUpdate = WorkbenchUser(uid, Some(user.googleSubjectId), user.email) + directoryDAO.updateGoogleSubjectId(toUpdate) + case Some(sub) => + //We don't support inviting a group account or pet service account + Future.failed[WorkbenchUser](new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.BadRequest, s"user ${user} already exists"))) + case None => directoryDAO.createUser(WorkbenchUser(user.id, Some(user.googleSubjectId), user.email)) + } + } yield updated + }(_ => Future.failed[WorkbenchUser](new WorkbenchExceptionWithErrorReport(ErrorReport(StatusCodes.Conflict, s"user ${user} already exists")))) + } yield user + def getSubjectFromEmail(email: WorkbenchEmail): Future[Option[WorkbenchSubject]] = directoryDAO.loadSubjectFromEmail(email) def getUserStatus(userId: WorkbenchUserId, userDetailsOnly: Boolean = false): Future[Option[UserStatus]] = { diff --git a/src/test/resources/reference.conf b/src/test/resources/reference.conf index 2a466c1f7a..351ab5fbf2 100644 --- a/src/test/resources/reference.conf +++ b/src/test/resources/reference.conf @@ -60,7 +60,7 @@ schemaLock { recheckTimeInterval = 5 maxTimeToWait = 60 instanceId = "sam-local" - schemaVersion = 1 + schemaVersion = 2 } petServiceAccount { diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/SamPackageObjectSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/SamPackageObjectSpec.scala new file mode 100644 index 0000000000..a6374e9e8c --- /dev/null +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/SamPackageObjectSpec.scala @@ -0,0 +1,17 @@ +package org.broadinstitute.dsde.workbench.sam + +import org.scalatest.{FlatSpec, Matchers} + +class SamPackageObjectSpec extends FlatSpec with Matchers{ + it should "generate unique identifier properly" in { + val current = 1534253386722L + val res = genWorkbenchUserId(current).value + res.length shouldBe(21) + res.substring(0, current.toString.length) shouldBe("2534253386722") + + // validate when currentMillis doesn't start + val current2 = 25342533867225L + val res2 = genWorkbenchUserId(current2).value + res2.substring(0, current2.toString.length) shouldBe("25342533867225") + } +} diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/TestSupport.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/TestSupport.scala index 535675c517..ad2dd1f7b3 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/TestSupport.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/TestSupport.scala @@ -1,12 +1,80 @@ package org.broadinstitute.dsde.workbench.sam -import scala.concurrent.{Await, Awaitable} +import akka.actor.ActorSystem +import akka.http.scaladsl.model.headers.RawHeader +import akka.stream.Materializer +import com.typesafe.config.ConfigFactory +import net.ceedubs.ficus.Ficus._ +import org.broadinstitute.dsde.workbench.dataaccess.PubSubNotificationDAO +import org.broadinstitute.dsde.workbench.google.mock.{MockGoogleDirectoryDAO, MockGoogleIamDAO, MockGooglePubSubDAO, MockGoogleStorageDAO} +import org.broadinstitute.dsde.workbench.google.{GoogleDirectoryDAO, GoogleIamDAO} +import org.broadinstitute.dsde.workbench.model._ +import org.broadinstitute.dsde.workbench.sam.api._ +import org.broadinstitute.dsde.workbench.sam.config.{GoogleServicesConfig, PetServiceAccountConfig, _} +import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO +import org.broadinstitute.dsde.workbench.sam.google.{GoogleExtensionRoutes, GoogleExtensions, GoogleKeyCache} +import org.broadinstitute.dsde.workbench.sam.model._ +import org.broadinstitute.dsde.workbench.sam.openam.MockAccessPolicyDAO +import org.broadinstitute.dsde.workbench.sam.service._ + import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Awaitable, ExecutionContext} /** * Created by dvoet on 6/27/17. */ -trait TestSupport { +trait TestSupport{ def runAndWait[T](f: Awaitable[T]): T = Await.result(f, Duration.Inf) } -object TestSupport extends TestSupport \ No newline at end of file +object TestSupport extends TestSupport{ + val config = ConfigFactory.load() + val petServiceAccountConfig = config.as[PetServiceAccountConfig]("petServiceAccount") + val googleServicesConfig = config.as[GoogleServicesConfig]("googleServices") + val configResourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.map(rt => rt.name -> rt).toMap + val defaultUserEmail = WorkbenchEmail("newuser@new.com") + def proxyEmail(workbenchUserId: WorkbenchUserId) = WorkbenchEmail(s"PROXY_$workbenchUserId@${googleServicesConfig.appsDomain}") + def googleSubjectIdHeaderWithId(googleSubjectId: GoogleSubjectId) = RawHeader(api.googleSubjectIdHeader, googleSubjectId.value) + def genGoogleSubjectId(): GoogleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) + + def genGoogleSubjectIdHeader = RawHeader(api.googleSubjectIdHeader, genRandom(System.currentTimeMillis())) + val defaultEmailHeader = RawHeader(api.emailHeader, defaultUserEmail.value) + def genDefaultEmailHeader(workbenchEmail: WorkbenchEmail) = RawHeader(api.emailHeader, workbenchEmail.value) + + def genSamDependencies(resourceTypes: Map[ResourceTypeName, ResourceType] = Map.empty, googIamDAO: Option[GoogleIamDAO] = None, googleServicesConfig: GoogleServicesConfig = googleServicesConfig, cloudExtensions: Option[CloudExtensions] = None, googleDirectoryDAO: Option[GoogleDirectoryDAO] = None)(implicit system: ActorSystem, executionContext: ExecutionContext) = { + val googleDirectoryDAO = new MockGoogleDirectoryDAO() + val directoryDAO = new MockDirectoryDAO() + val googleIamDAO = googIamDAO.getOrElse(new MockGoogleIamDAO()) + val policyDAO = new MockAccessPolicyDAO() + val pubSubDAO = new MockGooglePubSubDAO() + val googleStorageDAO = new MockGoogleStorageDAO() + val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") + val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) + val googleExt = cloudExtensions.getOrElse(new GoogleExtensions( + directoryDAO, + policyDAO, + googleDirectoryDAO, + null, + googleIamDAO, + null, + null, + cloudKeyCache, + notificationDAO, + googleServicesConfig, + petServiceAccountConfig, + configResourceTypes(CloudExtensions.resourceTypeName))) + val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, "example.com") + val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, "example.com") + + SamDependencies(mockResourceService, new UserService(directoryDAO, googleExt), new StatusService(directoryDAO, googleExt), mockManagedGroupService, directoryDAO, googleExt) + } + + def genSamRoutes(samDependencies: SamDependencies)(implicit system: ActorSystem, executionContext: ExecutionContext, materializer: Materializer): SamRoutes = new SamRoutes(samDependencies.resourceService, samDependencies.userService, samDependencies.statusService, samDependencies.managedGroupService, null, samDependencies.directoryDAO) + with StandardUserInfoDirectives + with GoogleExtensionRoutes { + override val cloudExtensions: CloudExtensions = samDependencies.cloudExtensions + override val googleExtensions: GoogleExtensions = if(samDependencies.cloudExtensions.isInstanceOf[GoogleExtensions]) samDependencies.cloudExtensions.asInstanceOf[GoogleExtensions] else null + val googleKeyCache = if(samDependencies.cloudExtensions.isInstanceOf[GoogleExtensions])samDependencies.cloudExtensions.asInstanceOf[GoogleExtensions].googleKeyCache else null + } +} + +final case class SamDependencies(resourceService: ResourceService, userService: UserService, statusService: StatusService, managedGroupService: ManagedGroupService, directoryDAO: MockDirectoryDAO, val cloudExtensions: CloudExtensions) \ No newline at end of file diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesSpec.scala index 441df265a1..30164bb21a 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesSpec.scala @@ -1,9 +1,11 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.headers.OAuth2BearerToken import akka.http.scaladsl.model.{StatusCode, StatusCodes} import akka.http.scaladsl.testkit.ScalatestRouteTest +import org.broadinstitute.dsde.workbench.model import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._ import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.sam.TestSupport @@ -30,7 +32,8 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT private val managedGroupResourceType = ResourceType(ManagedGroupService.managedGroupTypeName, resourceActionPatterns, defaultRoles, ManagedGroupService.adminRoleName) private val resourceTypes = Map(managedGroupResourceType.name -> managedGroupResourceType) private val groupId = "foo" - private val defaultNewUser = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), WorkbenchEmail("newGuy@organization.org"), 0) + private val defaultNewUser = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, WorkbenchEmail("newGuy@organization.org"), 0) + private def defaultGoogleSubjectId: GoogleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) def assertGroupDoesNotExist(samRoutes: TestSamRoutes, groupId: String = groupId): Unit = { Get(s"/api/group/$groupId") ~> samRoutes.route ~> check { @@ -59,7 +62,7 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT // Makes an anonymous object for a user acting on the same data as the user specified in samRoutes def makeOtherUser(samRoutes: TestSamRoutes, userInfo: UserInfo = defaultNewUser) = new { - runAndWait(samRoutes.userService.createUser(WorkbenchUser(userInfo.userId, userInfo.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(userInfo.userId, defaultGoogleSubjectId, userInfo.userEmail))) val email = userInfo.userEmail val routes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, userInfo, samRoutes.mockDirectoryDao) } @@ -74,7 +77,7 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT assertCreateGroup(defaultRoutes) assertGetGroup(defaultRoutes) - val theDude = UserInfo(OAuth2BearerToken("tokenDude"), WorkbenchUserId("ElDudarino"), WorkbenchEmail("ElDudarino@example.com"), 0) + val theDude = UserInfo(OAuth2BearerToken("tokenDude"), WorkbenchUserId("ElDudarino"), None, WorkbenchEmail("ElDudarino@example.com"), 0) val dudesRoutes = new TestSamRoutes(defaultRoutes.resourceService, defaultRoutes.userService, defaultRoutes.statusService, defaultRoutes.managedGroupService, theDude, defaultRoutes.mockDirectoryDao) body(dudesRoutes) @@ -92,13 +95,13 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) assertCreateGroup(samRoutes) assertGetGroup(samRoutes) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(newGuy.userId, newGuy.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(newGuy.userId, defaultGoogleSubjectId, newGuy.userEmail))) setGroupMembers(samRoutes, Set(newGuyEmail), expectedStatus = StatusCodes.Created) @@ -117,7 +120,7 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT assertCreateGroup(samRoutes) val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) @@ -388,7 +391,7 @@ class ManagedGroupRoutesSpec extends FlatSpec with Matchers with ScalatestRouteT val samRoutes = TestSamRoutes(resourceTypes) val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesV1Spec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesV1Spec.scala index a4635044af..65e696761d 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesV1Spec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ManagedGroupRoutesV1Spec.scala @@ -1,4 +1,5 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.headers.OAuth2BearerToken @@ -6,7 +7,6 @@ import akka.http.scaladsl.model.{StatusCode, StatusCodes} import akka.http.scaladsl.testkit.ScalatestRouteTest import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._ import org.broadinstitute.dsde.workbench.model._ -import org.broadinstitute.dsde.workbench.sam.TestSupport import org.broadinstitute.dsde.workbench.sam.model._ import org.broadinstitute.dsde.workbench.sam.service.ManagedGroupService import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers} @@ -30,7 +30,8 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout private val managedGroupResourceType = ResourceType(ManagedGroupService.managedGroupTypeName, resourceActionPatterns, defaultRoles, ManagedGroupService.adminRoleName) private val resourceTypes = Map(managedGroupResourceType.name -> managedGroupResourceType) private val groupId = "foo" - private val defaultNewUser = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), WorkbenchEmail("newGuy@organization.org"), 0) + private val defaultNewUser = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, WorkbenchEmail("newGuy@organization.org"), 0) + private val defaultGoogleSubjectId = GoogleSubjectId("googleSubjectId1") def assertGroupDoesNotExist(samRoutes: TestSamRoutes, groupId: String = groupId): Unit = { Get(s"/api/groups/v1/$groupId") ~> samRoutes.route ~> check { @@ -59,7 +60,7 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout // Makes an anonymous object for a user acting on the same data as the user specified in samRoutes def makeOtherUser(samRoutes: TestSamRoutes, userInfo: UserInfo = defaultNewUser) = new { - runAndWait(samRoutes.userService.createUser(WorkbenchUser(userInfo.userId, userInfo.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(userInfo.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), userInfo.userEmail))) val email = userInfo.userEmail val routes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, userInfo, samRoutes.mockDirectoryDao) } @@ -74,7 +75,7 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout assertCreateGroup(defaultRoutes) assertGetGroup(defaultRoutes) - val theDude = UserInfo(OAuth2BearerToken("tokenDude"), WorkbenchUserId("ElDudarino"), WorkbenchEmail("ElDudarino@example.com"), 0) + val theDude = UserInfo(OAuth2BearerToken("tokenDude"), WorkbenchUserId("ElDudarino"), None, WorkbenchEmail("ElDudarino@example.com"), 0) val dudesRoutes = new TestSamRoutes(defaultRoutes.resourceService, defaultRoutes.userService, defaultRoutes.statusService, defaultRoutes.managedGroupService, theDude, defaultRoutes.mockDirectoryDao) body(dudesRoutes) @@ -92,13 +93,13 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) assertCreateGroup(samRoutes) assertGetGroup(samRoutes) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(newGuy.userId, newGuy.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(newGuy.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), newGuy.userEmail))) setGroupMembers(samRoutes, Set(newGuyEmail), expectedStatus = StatusCodes.Created) @@ -117,7 +118,7 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout assertCreateGroup(samRoutes) val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) @@ -388,7 +389,7 @@ class ManagedGroupRoutesV1Spec extends FlatSpec with Matchers with ScalatestRout val samRoutes = TestSamRoutes(resourceTypes) val newGuyEmail = WorkbenchEmail("newGuy@organization.org") - val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), newGuyEmail, 0) + val newGuy = UserInfo(OAuth2BearerToken("newToken"), WorkbenchUserId("NewGuy"), None, newGuyEmail, 0) val newGuyRoutes = new TestSamRoutes(samRoutes.resourceService, samRoutes.userService, samRoutes.statusService, samRoutes.managedGroupService, newGuy, samRoutes.mockDirectoryDao) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/MockUserInfoDirectives.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/MockUserInfoDirectives.scala index 40210b2439..4182cafbb3 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/MockUserInfoDirectives.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/MockUserInfoDirectives.scala @@ -1,4 +1,6 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api + import akka.http.scaladsl.model.headers.OAuth2BearerToken import akka.http.scaladsl.server.Directive1 import akka.http.scaladsl.server.Directives.provide @@ -7,15 +9,15 @@ import org.broadinstitute.dsde.workbench.model.{UserInfo, WorkbenchEmail, Workbe /** * Created by dvoet on 6/7/17. */ -trait MockUserInfoDirectives extends UserInfoDirectives { +trait MockUserInfoDirectives extends StandardUserInfoDirectives { val userInfo: UserInfo - val petSAdomain = "\\S+@\\S+\\.iam\\.gserviceaccount\\.com".r + override val petSAdomain = "\\S+@\\S+\\.iam\\.gserviceaccount\\.com".r private def isPetSA(email: String) = { petSAdomain.pattern.matcher(email).matches } override def requireUserInfo: Directive1[UserInfo] = provide(if(isPetSA(userInfo.userEmail.value)) { - new UserInfo(OAuth2BearerToken(""),WorkbenchUserId("newuser"), WorkbenchEmail("newuser@new.com"), userInfo.tokenExpiresIn) + new UserInfo(OAuth2BearerToken(""),WorkbenchUserId("newuser"), None, WorkbenchEmail("newuser@new.com"), userInfo.tokenExpiresIn) } else userInfo ) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesSpec.scala index 5b209c247c..11d7332ad5 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesSpec.scala @@ -1,4 +1,5 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes @@ -24,7 +25,8 @@ import spray.json.{JsBoolean, JsValue} */ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest with TestSupport with AppendedClues { - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) + val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), None, WorkbenchEmail("user1@example.com"), 0) + def defaultGoogleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) private val config = ConfigFactory.load() private val resourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.toSet @@ -50,7 +52,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest val mockStatusService = new StatusService(directoryDAO, NoExtensions) val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, accessPolicyDAO, directoryDAO, NoExtensions, emailDomain) - mockUserService.createUser(WorkbenchUser(defaultUserInfo.userId, defaultUserInfo.userEmail)) + mockUserService.createUser(CreateWorkbenchUserAPI(defaultUserInfo.userId, defaultGoogleSubjectId, defaultUserInfo.userEmail)) new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, userInfo, directoryDAO) } @@ -151,8 +153,8 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest resourceType.isAuthDomainConstrainable shouldEqual true val authDomainId = ResourceId("myAuthDomain") - val otherUser = UserInfo(OAuth2BearerToken("magicString"), WorkbenchUserId("bugsBunny"), WorkbenchEmail("bugsford_bunnington@example.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(otherUser.userId, otherUser.userEmail))) + val otherUser = UserInfo(OAuth2BearerToken("magicString"), WorkbenchUserId("bugsBunny"), None, WorkbenchEmail("bugsford_bunnington@example.com"), 0) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(otherUser.userId, defaultGoogleSubjectId, otherUser.userEmail))) runAndWait(samRoutes.managedGroupService.createManagedGroup(authDomainId, otherUser)) val authDomain = Set(WorkbenchGroupName(authDomainId.value)) @@ -222,7 +224,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest private def responsePayloadClue(str: String): String = s" -> Here is the response payload: $str" private def createUserResourcePolicy(members: AccessPolicyMembership, resourceType: ResourceType, samRoutes: TestSamRoutes, resourceId: ResourceId, policyName: AccessPolicyName): Unit = { - val user = WorkbenchUser(samRoutes.userInfo.userId, samRoutes.userInfo.userEmail) + val user = CreateWorkbenchUserAPI(samRoutes.userInfo.userId, defaultGoogleSubjectId, samRoutes.userInfo.userEmail) findOrCreateUser(user, samRoutes.userService) Post(s"/api/resource/${resourceType.name}/${resourceId.value}") ~> samRoutes.route ~> check { @@ -235,7 +237,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest } } - private def findOrCreateUser(user: WorkbenchUser, userService: UserService): UserStatus = { + private def findOrCreateUser(user: CreateWorkbenchUserAPI, userService: UserService): UserStatus = { runAndWait(userService.getUserStatus(user.id)) match { case Some(userStatus) => userStatus case None => runAndWait(userService.createUser(user)) @@ -305,7 +307,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -324,7 +326,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -349,7 +351,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -371,7 +373,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -393,7 +395,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) val badEmail = WorkbenchEmail("null@bar.baz") @@ -434,7 +436,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest it should "404 when creating a policy on a resource that the user doesnt have permission to see" in { val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0)) + val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0)) val members = AccessPolicyMembership(Set(WorkbenchEmail("foo@bar.baz")), Set(ResourceAction("can_compute")), Set.empty) //Create a resource @@ -490,7 +492,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest it should "404 when listing policies for a resource when user can't see the resource" in { val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0)) + val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0)) //Create the resource Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { @@ -557,8 +559,8 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest val samRoutes = createSamRoutes(Map(resourceType.name -> resourceType)) runAndWait(samRoutes.resourceService.createResourceType(resourceType)) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com")))) - runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(WorkbenchUserId(""), defaultGoogleSubjectId, WorkbenchEmail("user2@example.com")))) + runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0))) //Verify resource exists by checking for conflict on recreate Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { @@ -594,7 +596,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -613,7 +615,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("owner"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -628,7 +630,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that we don't create user val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -643,7 +645,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that owner role does not have alter_policies val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, SamResourceActionPatterns.sharePolicy), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("splat"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -658,9 +660,9 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that testUser creates resource, not defaultUser which calls the PUT val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(ResourceAction("can_compute")))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com"), 0) + val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), None, WorkbenchEmail("testuser@foo.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(testUser.userId, testUser.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(testUser.userId, defaultGoogleSubjectId, testUser.userEmail))) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), testUser)) @@ -673,7 +675,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // happy case val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -690,7 +692,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // happy case val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.sharePolicy, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("owner"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -707,7 +709,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that we don't create user val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -724,7 +726,7 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that owner role does not have alter_policies val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, SamResourceActionPatterns.sharePolicy), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("splat"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), defaultGoogleSubjectId, WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -741,9 +743,9 @@ class ResourceRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest // differs from happy case in that testUser creates resource, not defaultUser which calls the PUT val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(ResourceAction("can_compute")))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com"), 0) + val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), None, WorkbenchEmail("testuser@foo.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(testUser.userId, testUser.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(testUser.userId, defaultGoogleSubjectId, testUser.userEmail))) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), testUser)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesV1Spec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesV1Spec.scala index e1d9dc988a..7068e3d975 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesV1Spec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/ResourceRoutesV1Spec.scala @@ -1,4 +1,5 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes @@ -24,7 +25,7 @@ import spray.json.{JsBoolean, JsValue} */ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTest with TestSupport with AppendedClues { - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) + val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), None, WorkbenchEmail("user1@example.com"), 0) private val config = ConfigFactory.load() private val resourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.toSet @@ -50,7 +51,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes val mockStatusService = new StatusService(directoryDAO, NoExtensions) val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, accessPolicyDAO, directoryDAO, NoExtensions, emailDomain) - mockUserService.createUser(WorkbenchUser(defaultUserInfo.userId, defaultUserInfo.userEmail)) + mockUserService.createUser(CreateWorkbenchUserAPI(defaultUserInfo.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), defaultUserInfo.userEmail)) new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, userInfo, directoryDAO) } @@ -151,8 +152,8 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes resourceType.isAuthDomainConstrainable shouldEqual true val authDomainId = ResourceId("myAuthDomain") - val otherUser = UserInfo(OAuth2BearerToken("magicString"), WorkbenchUserId("bugsBunny"), WorkbenchEmail("bugsford_bunnington@example.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(otherUser.userId, otherUser.userEmail))) + val otherUser = UserInfo(OAuth2BearerToken("magicString"), WorkbenchUserId("bugsBunny"), None, WorkbenchEmail("bugsford_bunnington@example.com"), 0) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(otherUser.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), otherUser.userEmail))) runAndWait(samRoutes.managedGroupService.createManagedGroup(authDomainId, otherUser)) val authDomain = Set(WorkbenchGroupName(authDomainId.value)) @@ -222,7 +223,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes private def responsePayloadClue(str: String): String = s" -> Here is the response payload: $str" private def createUserResourcePolicy(members: AccessPolicyMembership, resourceType: ResourceType, samRoutes: TestSamRoutes, resourceId: ResourceId, policyName: AccessPolicyName): Unit = { - val user = WorkbenchUser(samRoutes.userInfo.userId, samRoutes.userInfo.userEmail) + val user = CreateWorkbenchUserAPI(samRoutes.userInfo.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), samRoutes.userInfo.userEmail) findOrCreateUser(user, samRoutes.userService) Post(s"/api/resources/v1/${resourceType.name}/${resourceId.value}") ~> samRoutes.route ~> check { @@ -235,7 +236,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes } } - private def findOrCreateUser(user: WorkbenchUser, userService: UserService): UserStatus = { + private def findOrCreateUser(user: CreateWorkbenchUserAPI, userService: UserService): UserStatus = { runAndWait(userService.getUserStatus(user.id)) match { case Some(userStatus) => userStatus case None => runAndWait(userService.createUser(user)) @@ -305,7 +306,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -324,7 +325,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -349,7 +350,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -371,7 +372,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) @@ -393,7 +394,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes status shouldEqual StatusCodes.NoContent } - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.userService.createUser(testUser)) val badEmail = WorkbenchEmail("null@bar.baz") @@ -434,7 +435,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes it should "404 when creating a policy on a resource that the user doesnt have permission to see" in { val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0)) + val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0)) val members = AccessPolicyMembership(Set(WorkbenchEmail("foo@bar.baz")), Set(ResourceAction("can_compute")), Set.empty) //Create a resource @@ -490,7 +491,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes it should "404 when listing policies for a resource when user can't see the resource" in { val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0)) + val otherUserSamRoutes = TestSamRoutes(Map(resourceType.name -> resourceType), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0)) //Create the resource Post(s"/api/resources/v1/${resourceType.name}/foo") ~> samRoutes.route ~> check { @@ -557,8 +558,8 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes val samRoutes = createSamRoutes(Map(resourceType.name -> resourceType)) runAndWait(samRoutes.resourceService.createResourceType(resourceType)) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com")))) - runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), WorkbenchEmail("user2@example.com"), 0))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(WorkbenchUserId("user2"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("user2@example.com")))) + runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user2"), None, WorkbenchEmail("user2@example.com"), 0))) //Verify resource exists by checking for conflict on recreate Post(s"/api/resources/v1/${resourceType.name}/foo") ~> samRoutes.route ~> check { @@ -594,7 +595,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -613,7 +614,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("owner"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -628,7 +629,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that we don't create user val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -643,7 +644,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that owner role does not have alter_policies val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, SamResourceActionPatterns.sharePolicy), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("splat"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -658,9 +659,9 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that testUser creates resource, not defaultUser which calls the PUT val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(ResourceAction("can_compute")))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com"), 0) + val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), None, WorkbenchEmail("testuser@foo.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(testUser.userId, testUser.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(testUser.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), testUser.userEmail))) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), testUser)) @@ -673,7 +674,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // happy case val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -690,7 +691,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // happy case val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.sharePolicy, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("owner"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -707,7 +708,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that we don't create user val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.alterPolicies))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -724,7 +725,7 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that owner role does not have alter_policies val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, SamResourceActionPatterns.sharePolicy), Set(ResourceRole(ResourceRoleName("owner"), Set(SamResourceActions.sharePolicy(AccessPolicyName("splat"))))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = WorkbenchUser(WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com")) + val testUser = CreateWorkbenchUserAPI(WorkbenchUserId("testuser"), GoogleSubjectId(genRandom(System.currentTimeMillis())), WorkbenchEmail("testuser@foo.com")) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), defaultUserInfo)) @@ -741,9 +742,9 @@ class ResourceRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTes // differs from happy case in that testUser creates resource, not defaultUser which calls the PUT val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false)), Set(ResourceRole(ResourceRoleName("owner"), Set(ResourceAction("can_compute")))), ResourceRoleName("owner")) val samRoutes = TestSamRoutes(Map(resourceType.name -> resourceType)) - val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), WorkbenchEmail("testuser@foo.com"), 0) + val testUser = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("testuser"), None, WorkbenchEmail("testuser@foo.com"), 0) - runAndWait(samRoutes.userService.createUser(WorkbenchUser(testUser.userId, testUser.userEmail))) + runAndWait(samRoutes.userService.createUser(CreateWorkbenchUserAPI(testUser.userId, GoogleSubjectId(genRandom(System.currentTimeMillis())), testUser.userEmail))) runAndWait(samRoutes.resourceService.createResource(resourceType, ResourceId("foo"), testUser)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/StatusRouteSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/StatusRouteSpec.scala index a7ba8b0d59..7d03236f56 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/StatusRouteSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/StatusRouteSpec.scala @@ -41,7 +41,7 @@ class StatusRouteSpec extends FlatSpec with Matchers with ScalatestRouteTest wit val mockStatusService = new StatusService(directoryDAO, NoExtensions) val mockManagedGroupService = new ManagedGroupService(mockResourceService, Map.empty, policyDAO, directoryDAO, NoExtensions, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), WorkbenchUserId(""), WorkbenchEmail(""), 0), directoryDAO) + val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), WorkbenchUserId(""), None, WorkbenchEmail(""), 0), directoryDAO) Get("/status") ~> samRoutes.route ~> check { responseAs[StatusCheckResponse].ok shouldEqual false diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/TestSamRoutes.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/TestSamRoutes.scala index db9b8a64c9..1bc9932871 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/TestSamRoutes.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/TestSamRoutes.scala @@ -30,8 +30,8 @@ class TestSamRoutes(resourceService: ResourceService, userService: UserService, } object TestSamRoutes { - - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) + val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), None, WorkbenchEmail("user1@example.com"), 0) + val defaultGoogleSubjectId = GoogleSubjectId("googleSubjectId1") def apply(resourceTypes: Map[ResourceTypeName, ResourceType], userInfo: UserInfo = defaultUserInfo)(implicit system: ActorSystem, materializer: Materializer, executionContext: ExecutionContext) = { // need to make sure MockDirectoryDAO and MockAccessPolicyDAO share the same groups @@ -44,7 +44,7 @@ object TestSamRoutes { val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, NoExtensions, emailDomain) val mockUserService = new UserService(directoryDAO, NoExtensions) val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, NoExtensions, emailDomain) - TestSupport.runAndWait(mockUserService.createUser(WorkbenchUser(userInfo.userId, userInfo.userEmail))) + TestSupport.runAndWait(mockUserService.createUser(CreateWorkbenchUserAPI(userInfo.userId, defaultGoogleSubjectId, userInfo.userEmail))) val allUsersGroup = TestSupport.runAndWait(NoExtensions.getOrCreateAllUsersGroup(directoryDAO)) TestSupport.runAndWait(googleDirectoryDAO.createGroup(allUsersGroup.id.toString, allUsersGroup.email)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesSpec.scala index 9a54e18d68..2a000f73df 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesSpec.scala @@ -1,16 +1,19 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.OAuth2BearerToken +import akka.http.scaladsl.model.headers.{OAuth2BearerToken, RawHeader} import akka.http.scaladsl.testkit.ScalatestRouteTest +import org.broadinstitute.dsde.workbench.google.GoogleDirectoryDAO import org.broadinstitute.dsde.workbench.google.mock.MockGoogleDirectoryDAO import org.broadinstitute.dsde.workbench.model._ -import org.broadinstitute.dsde.workbench.sam.TestSupport +import org.broadinstitute.dsde.workbench.model.google.{ServiceAccount, ServiceAccountSubjectId} +import org.broadinstitute.dsde.workbench.sam.TestSupport.{genSamDependencies, genSamRoutes} import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import org.broadinstitute.dsde.workbench.sam.model._ -import org.broadinstitute.dsde.workbench.sam.service.{NoExtensions, StatusService, UserService} +import org.broadinstitute.dsde.workbench.sam.service.{CloudExtensions, NoExtensions, StatusService, UserService} import org.scalatest.mockito.MockitoSugar import org.scalatest.{FlatSpec, Matchers} @@ -18,198 +21,232 @@ import scala.concurrent.Future /** * Created by dvoet on 6/7/17. */ -class UserRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest with MockitoSugar with TestSupport { - val defaultUserId = WorkbenchUserId("newuser") - val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val adminUserId = WorkbenchUserId("adminuser") - val adminUserEmail = WorkbenchEmail("adminuser@new.com") - val petSAUserId = WorkbenchUserId("123") - val petSAEmail = WorkbenchEmail("pet-newuser@test.iam.gserviceaccount.com") - - def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { - val directoryDAO = new MockDirectoryDAO() - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO) - testCode(samRoutes) - } - - def setupAdminsGroup(googleDirectoryDAO: MockGoogleDirectoryDAO): Future[WorkbenchEmail] = { - val adminGroupEmail = WorkbenchEmail("fc-admins@dev.test.firecloud.org") - for { - _ <- googleDirectoryDAO.createGroup(WorkbenchGroupName("fc-admins"), adminGroupEmail) - _ <- googleDirectoryDAO.addMemberToGroup(adminGroupEmail, WorkbenchEmail(adminUserEmail.value)) - } yield adminGroupEmail - } - - def withAdminRoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - - val adminGroupEmail = runAndWait(setupAdminsGroup(googleDirectoryDAO)) - - val cloudExtensions = new NoExtensions { - override def isWorkbenchAdmin(memberEmail: WorkbenchEmail): Future[Boolean] = googleDirectoryDAO.isGroupMember(adminGroupEmail, memberEmail) - } - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO, cloudExtensions) - val adminRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), adminUserId, adminUserEmail, 0), directoryDAO, cloudExtensions) - testCode(samRoutes, adminRoutes) - } - - def withSARoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { - val directoryDAO = new MockDirectoryDAO() - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO, NoExtensions) - val SARoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), petSAUserId, petSAEmail, 0), directoryDAO, NoExtensions) - testCode(samRoutes, SARoutes) - } - +class UserRoutesSpec extends UserRoutesSpecHelper { "POST /register/user" should "create user" in withDefaultRoutes { samRoutes => - Post("/register/user") ~> samRoutes.route ~> check { + val header = TestSupport.genGoogleSubjectIdHeader + Post("/register/user").withHeaders(header, TestSupport.defaultEmailHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + val res = responseAs[UserStatus] + res.userInfo.userSubjectId.value.length shouldBe 21 + res.userInfo.userEmail shouldBe defaultUserEmail + res.enabled shouldBe Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true) } - Post("/register/user") ~> samRoutes.route ~> check { + Post("/register/user").withHeaders(header, TestSupport.defaultEmailHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Conflict } } - "GET /register/user" should "get the status of an enabled user" in withDefaultRoutes { samRoutes => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /register/user" should "get the status of an enabled user" in { + val (user, headers, _, routes) = createTestUser() - Get("/register/user") ~> samRoutes.route ~> check { + Get("/register/user").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - } - - - "POST /register/user" should "create a user" in withSARoutes{ (samRoutes, SARoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get("/register/user") ~> SARoutes.route ~> check { + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + } + } + + "POST /register/user" should "retrieve PET owner's user info if a PET account is provided" in { + val (user, _, samDep, routes) = createTestUser() + val petEmail = s"pet-${user.id.value}@test.iam.gserviceaccount.com" + val headers = List( + RawHeader(api.emailHeader, petEmail), + TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + //create a PET service account owned by test user + runAndWait(samDep.directoryDAO.createPetServiceAccount(PetServiceAccount(PetServiceAccountId(user.id, null), ServiceAccount(ServiceAccountSubjectId(user.googleSubjectId.get.value), WorkbenchEmail(petEmail), null)))) + Get("/register/user").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } - "GET /admin/user/{userSubjectId}" should "get the user status of a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /admin/user/{userSubjectId}" should "get the user status of a user (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Get(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/${user.id}").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } it should "not allow a non-admin to get the status of another user" in withAdminRoutes { (samRoutes, _) => - Get(s"/api/admin/user/$defaultUserId") ~> samRoutes.route ~> check { + Get(s"/api/admin/user/$defaultUserId").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "GET /admin/user/email/{email}" should "get the user status of a user by email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /admin/user/email/{email}" should "get the user status of a user by email (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Get(s"/api/admin/user/email/$defaultUserEmail") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/${user.email}").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } it should "return 404 for an unknown user by email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Get(s"/api/admin/user/email/XXX${defaultUserEmail}XXX") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/XXX${defaultUserEmail}XXX").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "return 404 for an group's email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Get(s"/api/admin/user/email/fc-admins@dev.test.firecloud.org") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/fc-admins@dev.test.firecloud.org").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "not allow a non-admin to get the status of another user" in withAdminRoutes { (samRoutes, _) => - Get(s"/api/admin/user/email/$defaultUserEmail") ~> samRoutes.route ~> check { + Get(s"/api/admin/user/email/$defaultUserEmail").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "PUT /admin/user/{userSubjectId}/(re|dis)able" should "disable and then re-enable a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "PUT /admin/user/{userSubjectId}/(re|dis)able" should "disable and then re-enable a user (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Put(s"/api/admin/user/$defaultUserId/disable") ~> adminRoutes.route ~> check { + Put(s"/api/admin/user/${user.id}/disable").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> false, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails({user.id}, user.email), Map("ldap" -> false, "allUsersGroup" -> true, "google" -> true)) } - Put(s"/api/admin/user/$defaultUserId/enable") ~> adminRoutes.route ~> check { + Put(s"/api/admin/user/${user.id}/enable").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails({user.id}, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } it should "not allow a non-admin to enable or disable a user" in withAdminRoutes { (samRoutes, _) => - Put(s"/api/admin/user/$defaultUserId/disable") ~> samRoutes.route ~> check { + Put(s"/api/admin/user/$defaultUserId/disable").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } - Put(s"/api/admin/user/$defaultUserId/enable") ~> samRoutes.route ~> check { + Put(s"/api/admin/user/$defaultUserId/enable").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "DELETE /admin/user/{userSubjectId}" should "delete a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "DELETE /admin/user/{userSubjectId}" should "delete a user (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Delete(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + Delete(s"/api/admin/user/${user.id}").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK } - Get(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/${user.id}").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "not allow a non-admin to delete a user" in withAdminRoutes { (samRoutes, _) => - Delete(s"/api/admin/user/$defaultUserId") ~> samRoutes.route ~> check { + Delete(s"/api/admin/user/$defaultUserId").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "DELETE /admin/user/{userSubjectId}/petServiceAccount/{project}" should "delete a pet (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "DELETE /admin/user/{userSubjectId}/petServiceAccount/{project}" should "delete a pet (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject") ~> adminRoutes.route ~> check { + Delete(s"/api/admin/user/${user.id}/petServiceAccount/myproject").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } } it should "not allow a non-admin to delete a pet" in withAdminRoutes { (samRoutes, _) => - Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } } + +trait UserRoutesSpecHelper extends FlatSpec with Matchers with ScalatestRouteTest with MockitoSugar with TestSupport{ + val defaultUserId = WorkbenchUserId("newuser") + val defaultUserEmail = WorkbenchEmail("newuser@new.com") + + val adminUserId = WorkbenchUserId("adminuser") + val adminUserEmail = WorkbenchEmail("adminuser@new.com") + val adminHeaders = List( + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.userIdHeader, adminUserId.value), + RawHeader(api.emailHeader, adminUserEmail.value), + RawHeader(api.expiresInHeader, "1000"), + TestSupport.genGoogleSubjectIdHeader, + ) + + val petSAUserId = WorkbenchUserId("123") + val petSAEmail = WorkbenchEmail("pet-newuser@test.iam.gserviceaccount.com") + + + def setupAdminsGroup(googleDirectoryDAO: MockGoogleDirectoryDAO): Future[WorkbenchEmail] = { + val adminGroupEmail = WorkbenchEmail("fc-admins@dev.test.firecloud.org") + for { + _ <- googleDirectoryDAO.createGroup(WorkbenchGroupName("fc-admins"), adminGroupEmail) + _ <- googleDirectoryDAO.addMemberToGroup(adminGroupEmail, WorkbenchEmail(adminUserEmail.value)) + } yield adminGroupEmail + } + + def setUpAdminTest(): (WorkbenchUser, SamRoutes) = { + val googDirectoryDAO = new MockGoogleDirectoryDAO() + val adminGroupEmail = runAndWait(setupAdminsGroup(googDirectoryDAO)) + val cloudExtensions = new NoExtensions { + override def isWorkbenchAdmin(memberEmail: WorkbenchEmail): Future[Boolean] = googDirectoryDAO.isGroupMember(adminGroupEmail, memberEmail) + } + val (user, _, _, routes) = createTestUser(cloudExtensions = Some(cloudExtensions), googleDirectoryDAO = Some(googDirectoryDAO)) + //TODO: register adminUser. remove this once https://broadinstitute.atlassian.net/browse/GAWB-3747 is done + Post("/register/user/v1/").withHeaders(adminHeaders) ~> routes.route ~> check { + status shouldEqual StatusCodes.Created + } + (user, routes) + } + + def createTestUser(googSubjectId: Option[GoogleSubjectId] = None, cloudExtensions: Option[CloudExtensions] = None, googleDirectoryDAO: Option[GoogleDirectoryDAO] = None): (WorkbenchUser, List[RawHeader], SamDependencies, SamRoutes) = { + val googleSubjectId = googSubjectId.map(_.value).getOrElse(genRandom(System.currentTimeMillis())) + val googleSubjectheader = RawHeader(api.googleSubjectIdHeader, googleSubjectId) + val emailHeader = RawHeader(api.emailHeader, defaultUserEmail.value) + + val samDependencies = genSamDependencies(cloudExtensions = cloudExtensions, googleDirectoryDAO = googleDirectoryDAO) + val routes = genSamRoutes(samDependencies) + + // create a user + val user = Post("/register/user/v1/").withHeaders(googleSubjectheader, emailHeader) ~> routes.route ~> check { + status shouldEqual StatusCodes.Created + val res = responseAs[UserStatus] + res.userInfo.userEmail shouldBe defaultUserEmail + res.enabled shouldBe Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true) + + WorkbenchUser(res.userInfo.userSubjectId, Some(GoogleSubjectId(googleSubjectId)), res.userInfo.userEmail) + } + val headers = List( + RawHeader(api.emailHeader, user.email.value), + TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + (user, headers, samDependencies, routes) + } + + def withAdminRoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { + val googleDirectoryDAO = new MockGoogleDirectoryDAO() + val directoryDAO = new MockDirectoryDAO() + + val adminGroupEmail = runAndWait(setupAdminsGroup(googleDirectoryDAO)) + + val cloudExtensions = new NoExtensions { + override def isWorkbenchAdmin(memberEmail: WorkbenchEmail): Future[Boolean] = googleDirectoryDAO.isGroupMember(adminGroupEmail, memberEmail) + } + + val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, None, defaultUserEmail, 0), directoryDAO, cloudExtensions) + val adminRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), adminUserId, None, adminUserEmail, 0), directoryDAO, cloudExtensions) + testCode(samRoutes, adminRoutes) + } + + def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { + val directoryDAO = new MockDirectoryDAO() + + val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, None, defaultUserEmail, 0), directoryDAO) + testCode(samRoutes) + } +} \ No newline at end of file diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV1Spec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV1Spec.scala index 0dee9b91e3..34b6bea456 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV1Spec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV1Spec.scala @@ -1,220 +1,168 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.OAuth2BearerToken -import akka.http.scaladsl.testkit.ScalatestRouteTest -import org.broadinstitute.dsde.workbench.google.mock.MockGoogleDirectoryDAO -import org.broadinstitute.dsde.workbench.model._ -import org.broadinstitute.dsde.workbench.sam.TestSupport +import akka.http.scaladsl.model.headers.{OAuth2BearerToken, RawHeader} +import org.broadinstitute.dsde.workbench.model.google.{ServiceAccount, ServiceAccountSubjectId} +import org.broadinstitute.dsde.workbench.model.{PetServiceAccount, PetServiceAccountId, UserInfo, WorkbenchEmail} import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import org.broadinstitute.dsde.workbench.sam.model._ import org.broadinstitute.dsde.workbench.sam.service.{NoExtensions, StatusService, UserService} -import org.scalatest.mockito.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} - -import scala.concurrent.Future /** * Created by dvoet on 6/7/17. */ -class UserRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTest with MockitoSugar with TestSupport { - val defaultUserId = WorkbenchUserId("newuser") - val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val adminUserId = WorkbenchUserId("adminuser") - val adminUserEmail = WorkbenchEmail("adminuser@new.com") - val petSAUserId = WorkbenchUserId("123") - val petSAEmail = WorkbenchEmail("pet-newuser@test.iam.gserviceaccount.com") - - def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { - val directoryDAO = new MockDirectoryDAO() - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO) - testCode(samRoutes) - } - - def setupAdminsGroup(googleDirectoryDAO: MockGoogleDirectoryDAO): Future[WorkbenchEmail] = { - val adminGroupEmail = WorkbenchEmail("fc-admins@dev.test.firecloud.org") - for { - _ <- googleDirectoryDAO.createGroup(WorkbenchGroupName("fc-admins"), adminGroupEmail) - _ <- googleDirectoryDAO.addMemberToGroup(adminGroupEmail, WorkbenchEmail(adminUserEmail.value)) - } yield adminGroupEmail - } - - def withAdminRoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - - val adminGroupEmail = runAndWait(setupAdminsGroup(googleDirectoryDAO)) - - val cloudExtensions = new NoExtensions { - override def isWorkbenchAdmin(memberEmail: WorkbenchEmail): Future[Boolean] = googleDirectoryDAO.isGroupMember(adminGroupEmail, memberEmail) - } - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO, cloudExtensions) - val adminRoutes = new TestSamRoutes(null, new UserService(directoryDAO, cloudExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), adminUserId, adminUserEmail, 0), directoryDAO, cloudExtensions) - testCode(samRoutes, adminRoutes) - } - +class UserRoutesV1Spec extends UserRoutesSpecHelper{ def withSARoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { val directoryDAO = new MockDirectoryDAO() - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO, NoExtensions) - val SARoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), petSAUserId, petSAEmail, 0), directoryDAO, NoExtensions) + val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, None, defaultUserEmail, 0), directoryDAO, NoExtensions) + val SARoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), petSAUserId, None, petSAEmail, 0), directoryDAO, NoExtensions) testCode(samRoutes, SARoutes) } - "POST /register/user/v1/" should "create user" in withDefaultRoutes { samRoutes => - Post("/register/user/v1/") ~> samRoutes.route ~> check { + "POST /register/user/v1/" should "create user" in withDefaultRoutes{samRoutes => + val header = List(TestSupport.genGoogleSubjectIdHeader, TestSupport.defaultEmailHeader) + Post("/register/user/v1/").withHeaders(header) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + val res = responseAs[UserStatus] + res.userInfo.userSubjectId.value.length shouldBe 21 + res.userInfo.userEmail shouldBe defaultUserEmail + res.enabled shouldBe Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true) } - Post("/register/user/v1/") ~> samRoutes.route ~> check { + Post("/register/user/v1/").withHeaders(header) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Conflict } } - "GET /register/user/v1/" should "get the status of an enabled user" in withDefaultRoutes { samRoutes => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /register/user/v1/" should "get the status of an enabled user" in { + val (user, headers, samDep, routes) = createTestUser() + val googleSubjectIdHeader = TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get) - Get("/register/user/v1/") ~> samRoutes.route ~> check { + Get("/register/user/v1/").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } - Get("/register/user/v1?userDetailsOnly=true") ~> samRoutes.route ~> check { + Get("/register/user/v1?userDetailsOnly=true").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map.empty) - } - } - - - "POST /register/user/v1/" should "create a user" in withSARoutes{ (samRoutes, SARoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get("/register/user/v1/") ~> SARoutes.route ~> check { + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map.empty) + } + } + + "POST /register/user/v1/" should "retrieve PET owner's user info if a PET account is provided" in { + val (user, _, samDep, routes) = createTestUser() + val petEmail = s"pet-${user.id.value}@test.iam.gserviceaccount.com" + val headers = List( + RawHeader(api.emailHeader, petEmail), + TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + //create a PET service account owned by test user + runAndWait(samDep.directoryDAO.createPetServiceAccount(PetServiceAccount(PetServiceAccountId(user.id, null), ServiceAccount(ServiceAccountSubjectId(user.googleSubjectId.get.value), WorkbenchEmail(petEmail), null)))) + Get("/register/user/v1").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } - "GET /admin/user/{userSubjectId}" should "get the user status of a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + "GET /admin/user/{userSubjectId}" should "get the user status of a user (as an admin)" in { + val (user, getRoutes) = setUpAdminTest() + Get(s"/api/admin/user/${user.id}").withHeaders(adminHeaders) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } - it should "not allow a non-admin to get the status of another user" in withAdminRoutes { (samRoutes, _) => - Get(s"/api/admin/user/$defaultUserId") ~> samRoutes.route ~> check { + it should "not allow a non-admin with userId to get the status of another user" in withAdminRoutes { (samRoutes, _) => + Get(s"/api/admin/user/$defaultUserId").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "GET /admin/user/email/{email}" should "get the user status of a user by email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /admin/user/email/{email}" should "get the user status of a user by email (as an admin)" in { + val (user, getRoutes) = setUpAdminTest() - Get(s"/api/admin/user/email/$defaultUserEmail") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/${user.email}").withHeaders(adminHeaders) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } it should "return 404 for an unknown user by email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Get(s"/api/admin/user/email/XXX${defaultUserEmail}XXX") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/XXX${defaultUserEmail}XXX").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "return 404 for an group's email (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Get(s"/api/admin/user/email/fc-admins@dev.test.firecloud.org") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/email/fc-admins@dev.test.firecloud.org").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } - it should "not allow a non-admin to get the status of another user" in withAdminRoutes { (samRoutes, _) => - Get(s"/api/admin/user/email/$defaultUserEmail") ~> samRoutes.route ~> check { + it should "not allow a non-admin with user email to get the status of another user" in withAdminRoutes { (samRoutes, _) => + Get(s"/api/admin/user/email/$defaultUserEmail").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "PUT /admin/user/{userSubjectId}/(re|dis)able" should "disable and then re-enable a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "PUT /admin/user/{userSubjectId}/(re|dis)able" should "disable and then re-enable a user (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Put(s"/api/admin/user/$defaultUserId/disable") ~> adminRoutes.route ~> check { + Put(s"/api/admin/user/${user.id}/disable").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> false, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> false, "allUsersGroup" -> true, "google" -> true)) } - Put(s"/api/admin/user/$defaultUserId/enable") ~> adminRoutes.route ~> check { + Put(s"/api/admin/user/${user.id}/enable").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(user.id, user.email), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) } } it should "not allow a non-admin to enable or disable a user" in withAdminRoutes { (samRoutes, _) => - Put(s"/api/admin/user/$defaultUserId/disable") ~> samRoutes.route ~> check { + Put(s"/api/admin/user/$defaultUserId/disable").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } - Put(s"/api/admin/user/$defaultUserId/enable") ~> samRoutes.route ~> check { + Put(s"/api/admin/user/$defaultUserId/enable").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } - "DELETE /admin/user/{userSubjectId}" should "delete a user (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "DELETE /admin/user/{userSubjectId}" should "delete a user (as an admin)" in { + val (user, adminRoutes) = setUpAdminTest() - Delete(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + Delete(s"/api/admin/user/${user.id}").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.OK } - Get(s"/api/admin/user/$defaultUserId") ~> adminRoutes.route ~> check { + Get(s"/api/admin/user/${user.id}").withHeaders(adminHeaders)~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "not allow a non-admin to delete a user" in withAdminRoutes { (samRoutes, _) => - Delete(s"/api/admin/user/$defaultUserId") ~> samRoutes.route ~> check { + Delete(s"/api/admin/user/$defaultUserId").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } "DELETE /admin/user/{userSubjectId}/petServiceAccount/{project}" should "delete a pet (as an admin)" in withAdminRoutes { (samRoutes, adminRoutes) => - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + val (user, adminRoutes) = setUpAdminTest() - Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject") ~> adminRoutes.route ~> check { + Delete(s"/api/admin/user/${user.id}/petServiceAccount/myproject").withHeaders(adminHeaders) ~> adminRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } } it should "not allow a non-admin to delete a pet" in withAdminRoutes { (samRoutes, _) => - Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Delete(s"/api/admin/user/$defaultUserId/petServiceAccount/myproject").withHeaders(adminHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV2Spec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV2Spec.scala index f24f6db9da..63d89c033b 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV2Spec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/api/UserRoutesV2Spec.scala @@ -1,96 +1,98 @@ -package org.broadinstitute.dsde.workbench.sam.api +package org.broadinstitute.dsde.workbench.sam +package api import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ -import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.OAuth2BearerToken -import akka.http.scaladsl.testkit.ScalatestRouteTest +import akka.http.scaladsl.model.headers.{OAuth2BearerToken, RawHeader} import org.broadinstitute.dsde.workbench.model._ -import org.broadinstitute.dsde.workbench.sam.TestSupport +import org.broadinstitute.dsde.workbench.model.google.{ServiceAccount, ServiceAccountSubjectId} +import org.broadinstitute.dsde.workbench.sam.TestSupport._ import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO +import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import org.broadinstitute.dsde.workbench.sam.model._ import org.broadinstitute.dsde.workbench.sam.service.{NoExtensions, StatusService, UserService} -import org.scalatest.{FlatSpec, Matchers} -import org.scalatest.mockito.MockitoSugar /** * Created by mtalbott on 8/8/18. */ -class UserRoutesV2Spec extends FlatSpec with Matchers with ScalatestRouteTest with MockitoSugar with TestSupport { - - val defaultUserId = WorkbenchUserId("newuser") - val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val petSAUserId = WorkbenchUserId("123") - val petSAEmail = WorkbenchEmail("pet-newuser@test.iam.gserviceaccount.com") - - def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { - val directoryDAO = new MockDirectoryDAO() - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO) - testCode(samRoutes) - } - +class UserRoutesV2Spec extends UserRoutesSpecHelper { def withSARoutes[T](testCode: (TestSamRoutes, TestSamRoutes) => T): T = { val directoryDAO = new MockDirectoryDAO() - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO, NoExtensions) - val SARoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), petSAUserId, petSAEmail, 0), directoryDAO, NoExtensions) + val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, None, defaultUserEmail, 0), directoryDAO, NoExtensions) + val SARoutes = new TestSamRoutes(null, new UserService(directoryDAO, NoExtensions), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), petSAUserId, None, petSAEmail, 0), directoryDAO, NoExtensions) testCode(samRoutes, SARoutes) } "POST /register/user/v2/self" should "create user" in withDefaultRoutes { samRoutes => - Post("/register/user/v2/self") ~> samRoutes.route ~> check { + val header = TestSupport.genGoogleSubjectIdHeader + + Post("/register/user/v2/self").withHeaders(header, TestSupport.defaultEmailHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + val res = responseAs[UserStatus] + res.userInfo.userSubjectId.value.length shouldBe 21 + res.userInfo.userEmail shouldBe defaultUserEmail + res.enabled shouldBe Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true) } - Post("/register/user/v2/self") ~> samRoutes.route ~> check { + Post("/register/user/v2/self").withHeaders(header, TestSupport.defaultEmailHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Conflict } } "GET /register/user/v2/self/info" should "get the status of an enabled user" in withDefaultRoutes { samRoutes => - Get("/register/user/v2/self/info") ~> samRoutes.route ~> check { + val googleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) + val firstGetHeaders = List( + defaultEmailHeader, + TestSupport.googleSubjectIdHeaderWithId(googleSubjectId), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + Get("/register/user/v2/self/info").withHeaders(firstGetHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } - - Post("/register/user/v2/self") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get("/register/user/v2/self/info") ~> samRoutes.route ~> check { + val (user, headers, samDep, routes) = createTestUser(googSubjectId = Some(googleSubjectId)) + Get("/register/user/v2/self/info").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatusInfo] shouldEqual UserStatusInfo(defaultUserId.value, defaultUserEmail.value, true) + responseAs[UserStatusInfo] shouldEqual UserStatusInfo(user.id.value, user.email.value, true) } } "GET /register/user/v2/self/diagnostics" should "get the diagnostic info for an enabled user" in withDefaultRoutes { samRoutes => - Get("/register/user/v2/self/diagnostics") ~> samRoutes.route ~> check { + val googleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) + val firstGetHeaders = List( + defaultEmailHeader, + TestSupport.googleSubjectIdHeaderWithId(googleSubjectId), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + Get("/register/user/v2/self/diagnostics").withHeaders(firstGetHeaders) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } + val (user, headers, samDep, routes) = createTestUser(googSubjectId = Some(googleSubjectId)) - Post("/register/user/v2/self") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get("/register/user/v2/self/diagnostics") ~> samRoutes.route ~> check { + Get("/register/user/v2/self/diagnostics").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK responseAs[UserStatusDiagnostics] shouldEqual UserStatusDiagnostics(true, true, true) } } - "POST /register/user/v2/self" should "create a user" in withSARoutes{ (samRoutes, SARoutes) => - Post("/register/user/v2/self") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get("/register/user/v2/self/info") ~> SARoutes.route ~> check { + "POST /register/user/v2/self" should "retrieve PET owner's user info if a PET account is provided" in { + val (user, _, samDep, routes) = createTestUser() + val petEmail = s"pet-${user.id.value}@test.iam.gserviceaccount.com" + val headers = List( + RawHeader(api.emailHeader, petEmail), + TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + //create a PET service account owned by test user + runAndWait(samDep.directoryDAO.createPetServiceAccount(PetServiceAccount(PetServiceAccountId(user.id, null), ServiceAccount(ServiceAccountSubjectId(user.googleSubjectId.get.value), WorkbenchEmail(petEmail), null)))) + + Get("/register/user/v2/self/info").withHeaders(headers) ~> routes.route ~> check { status shouldEqual StatusCodes.OK - responseAs[UserStatusInfo] shouldEqual UserStatusInfo(defaultUserId.value, defaultUserEmail.value, true) + responseAs[UserStatusInfo] shouldEqual UserStatusInfo(user.id.value, user.email.value, true) } } } diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAOSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAOSpec.scala index cd92582745..913dc425a0 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAOSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/LdapDirectoryDAOSpec.scala @@ -70,7 +70,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "create, read, delete users" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) assertResult(None) { runAndWait(dao.loadUser(user.id)) @@ -93,7 +93,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "add and read proxy group email" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) assertResult(user) { runAndWait(dao.createUser(user)) @@ -112,7 +112,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "create, read, delete pet service accounts" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val serviceAccountUniqueId = ServiceAccountSubjectId(UUID.randomUUID().toString) val serviceAccount = ServiceAccount(serviceAccountUniqueId, WorkbenchEmail("foo@bar.com"), ServiceAccountDisplayName("")) val project = GoogleProject("testproject") @@ -164,7 +164,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "list groups" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set(userId), WorkbenchEmail("g1@example.com")) @@ -189,11 +189,11 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "list flattened group users" in { val userId1 = WorkbenchUserId(UUID.randomUUID().toString) - val user1 = WorkbenchUser(userId1, WorkbenchEmail("foo@bar.com")) + val user1 = WorkbenchUser(userId1, None, WorkbenchEmail("foo@bar.com")) val userId2 = WorkbenchUserId(UUID.randomUUID().toString) - val user2 = WorkbenchUser(userId2, WorkbenchEmail("foo@bar.com")) + val user2 = WorkbenchUser(userId2, None, WorkbenchEmail("foo@bar.com")) val userId3 = WorkbenchUserId(UUID.randomUUID().toString) - val user3 = WorkbenchUser(userId3, WorkbenchEmail("foo@bar.com")) + val user3 = WorkbenchUser(userId3, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set(userId1), WorkbenchEmail("g1@example.com")) @@ -252,7 +252,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "handle circular groups" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set(userId), WorkbenchEmail("g1@example.com")) @@ -293,7 +293,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "add/remove groups" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set.empty, WorkbenchEmail("g1@example.com")) @@ -340,7 +340,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "handle different kinds of groups" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set(userId), WorkbenchEmail("g1@example.com")) @@ -369,7 +369,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "be case insensitive when checking for group membership" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) val groupName1 = WorkbenchGroupName(UUID.randomUUID().toString) val group1 = BasicWorkbenchGroup(groupName1, Set(userId), WorkbenchEmail("g1@example.com")) @@ -382,7 +382,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "get pet for user" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) runAndWait(dao.createUser(user)) @@ -447,7 +447,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with "JndiDirectoryDao loadSubjectEmail" should "fail if the user has not been created" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) assertResult(None) { runAndWait(dao.loadUser(user.id)) @@ -458,7 +458,7 @@ class LdapDirectoryDAOSpec extends FlatSpec with Matchers with TestSupport with it should "succeed if the user has been created" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) - val user = WorkbenchUser(userId, WorkbenchEmail("foo@bar.com")) + val user = WorkbenchUser(userId, None, WorkbenchEmail("foo@bar.com")) assertResult(None) { runAndWait(dao.loadUser(user.id)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/MockDirectoryDAO.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/MockDirectoryDAO.scala index 0509f96469..b9c6095308 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/MockDirectoryDAO.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/directory/MockDirectoryDAO.scala @@ -3,6 +3,7 @@ package org.broadinstitute.dsde.workbench.sam.directory import java.util.Date import akka.http.scaladsl.model.StatusCodes +import cats.implicits._ import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.model.google.ServiceAccountSubjectId @@ -24,6 +25,7 @@ class MockDirectoryDAO(private val groups: mutable.Map[WorkbenchGroupIdentity, W private val enabledUsers: mutable.Map[WorkbenchSubject, Unit] = new TrieMap() private val usersWithEmails: mutable.Map[WorkbenchEmail, WorkbenchUserId] = new TrieMap() + private val usersWithGoogleSubjectIds: mutable.Map[GoogleSubjectId, WorkbenchUserId] = new TrieMap() private val groupsWithEmails: mutable.Map[WorkbenchEmail, WorkbenchGroupName] = new TrieMap() private val petServiceAccountsByUser: mutable.Map[PetServiceAccountId, PetServiceAccount] = new TrieMap() private val petsWithEmails: mutable.Map[WorkbenchEmail, PetServiceAccountId] = new TrieMap() @@ -90,6 +92,8 @@ class MockDirectoryDAO(private val groups: mutable.Map[WorkbenchGroupIdentity, W } users += user.id -> user usersWithEmails += user.email -> user.id + user.googleSubjectId.map(gid => usersWithGoogleSubjectIds += gid -> user.id) + user } @@ -235,7 +239,7 @@ class MockDirectoryDAO(private val groups: mutable.Map[WorkbenchGroupIdentity, W private def addUserAttribute(userId: WorkbenchUserId, attrId: String, value: Any): Future[Unit] = { userAttributes.get(userId) match { case Some(attributes: Map[String, Any]) => attributes += attrId -> value - case None => userAttributes += userId -> (new TrieMap() += attrId -> value) + case _ => userAttributes += userId -> (new TrieMap() += attrId -> value) } Future.successful(()) } @@ -247,4 +251,13 @@ class MockDirectoryDAO(private val groups: mutable.Map[WorkbenchGroupIdentity, W } yield value.asInstanceOf[T] Future.successful(value) } + + override def loadSubjectFromGoogleSubjectId(googleSubjectId: GoogleSubjectId): Future[Option[WorkbenchSubject]] = { + val res = for{ + uid <- usersWithGoogleSubjectIds.get(googleSubjectId) + } yield uid + res.traverse(Future.successful) + } + + override def updateGoogleSubjectId(user: WorkbenchUser): Future[WorkbenchUser] = Future.successful{users + (user.id -> user); user} } \ No newline at end of file diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesSpec.scala index 12c75c31bc..7c5177e0ff 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesSpec.scala @@ -2,17 +2,18 @@ package org.broadinstitute.dsde.workbench.sam.google import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.OAuth2BearerToken +import akka.http.scaladsl.model.headers.{OAuth2BearerToken, RawHeader} import akka.http.scaladsl.testkit.ScalatestRouteTest import com.typesafe.config.ConfigFactory import net.ceedubs.ficus.Ficus._ import org.broadinstitute.dsde.workbench.dataaccess.PubSubNotificationDAO import org.broadinstitute.dsde.workbench.google.GoogleIamDAO -import org.broadinstitute.dsde.workbench.google.mock.{MockGoogleDirectoryDAO, MockGoogleIamDAO, MockGooglePubSubDAO, MockGoogleStorageDAO} +import org.broadinstitute.dsde.workbench.google.mock.{MockGoogleDirectoryDAO, MockGooglePubSubDAO, MockGoogleStorageDAO} import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._ import org.broadinstitute.dsde.workbench.model._ import org.broadinstitute.dsde.workbench.model.google._ -import org.broadinstitute.dsde.workbench.sam.TestSupport +import org.broadinstitute.dsde.workbench.sam.TestSupport.{genSamDependencies, genSamRoutes, googleSubjectIdHeaderWithId} +import org.broadinstitute.dsde.workbench.sam.{SamDependencies, TestSupport, api, genRandom} import org.broadinstitute.dsde.workbench.sam.api.TestSamRoutes import org.broadinstitute.dsde.workbench.sam.config.{GoogleServicesConfig, PetServiceAccountConfig, _} import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO @@ -30,111 +31,54 @@ import scala.concurrent.Future /** * Unit tests of GoogleExtensionRoutes. Can use real Google services. Must mock everything else. */ -class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRouteTest with TestSupport with MockitoSugar { - val defaultUserId = WorkbenchUserId("newuser123") - val defaultUserEmail = WorkbenchEmail("newuser@new.com") -/* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 - val defaultUserProxyEmail = WorkbenchEmail(s"newuser_$defaultUserId@${googleServicesConfig.appsDomain}") -*/ - val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_$defaultUserId@${googleServicesConfig.appsDomain}") -/**/ - - lazy val config = ConfigFactory.load() - lazy val petServiceAccountConfig = config.as[PetServiceAccountConfig]("petServiceAccount") - lazy val googleServicesConfig = config.as[GoogleServicesConfig]("googleServices") - - val configResourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.map(rt => rt.name -> rt).toMap - - def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, null, googleDirectoryDAO, null, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, googleExt), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - } - testCode(samRoutes) - } +class GoogleExtensionRoutesSpec extends GoogleExtensionRoutesSpecHelper { - "GET /api/google/user/petServiceAccount" should "get or create a pet service account for a user" in withDefaultRoutes { samRoutes => - // create a user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /api/google/user/petServiceAccount" should "get or create a pet service account for a user" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject").withHeaders(headers).withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // same result a second time - Get("/api/google/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject").withHeaders(headers).withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } } - "GET /api/google/user/proxyGroup/{email}" should "return a user's proxy group" in withDefaultRoutes { samRoutes => - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - // create a user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + "GET /api/google/user/proxyGroup/{email}" should "return a user's proxy group" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) - Get(s"/api/google/user/proxyGroup/$defaultUserEmail") ~> samRoutes.route ~> check { + Get(s"/api/google/user/proxyGroup/${user.email}").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] - response shouldBe defaultUserProxyEmail + response shouldBe TestSupport.proxyEmail(user.id) } } - it should "return a user's proxy group from a pet service account" in withDefaultRoutes { samRoutes => - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) + it should "return a user's proxy group from a pet service account" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) - // create a user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - val petEmail = Get("/api/google/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + val petEmail = Get("/api/google/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") response.value } - Get(s"/api/google/user/proxyGroup/$petEmail") ~> samRoutes.route ~> check { + Get(s"/api/google/user/proxyGroup/$petEmail").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] - response shouldBe defaultUserProxyEmail + response shouldBe TestSupport.proxyEmail(user.id) } } @@ -151,52 +95,30 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou private val resourceType = ResourceType(ResourceTypeName("rt"), Set(SamResourceActionPatterns.alterPolicies, ResourceActionPattern("can_compute", "", false), SamResourceActionPatterns.readPolicies), Set(ResourceRole(ResourceRoleName("owner"), Set(ResourceAction("alter_policies"), ResourceAction("read_policies")))), ResourceRoleName("owner")) "POST /api/google/policy/{resourceTypeName}/{resourceId}/{accessPolicyName}/sync" should "204 Create Google group for policy" in { +///* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 +// val defaultUserProxyEmail = WorkbenchEmail(s"user1_user123@${googleServicesConfig.appsDomain}") +//*/ +// val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_user123@${googleServicesConfig.appsDomain}") +///**/ val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user123"), WorkbenchEmail("user1@example.com"), 0) -/* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 - val defaultUserProxyEmail = WorkbenchEmail(s"user1_user123@${googleServicesConfig.appsDomain}") -*/ - val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_user123@${googleServicesConfig.appsDomain}") -/**/ - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) + val (user, headers, samDep) = createTestUser(resourceTypes) + val header = googleSubjectIdHeaderWithId(user.googleSubjectId.get) + val getRoutes = genSamRoutes(samDep) - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - //create user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } - - Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { + Post(s"/api/resource/${resourceType.name}/foo").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } import spray.json.DefaultJsonProtocol._ - val createdPolicy = Get(s"/api/resource/${resourceType.name}/foo/policies") ~> samRoutes.route ~> check { + val createdPolicy = Get(s"/api/resource/${resourceType.name}/foo/policies").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK responseAs[Seq[AccessPolicyResponseEntry]].find(_.policyName == AccessPolicyName(resourceType.ownerRoleName.value)).getOrElse(fail("created policy not returned by get request")) } import GoogleModelJsonSupport._ - Post(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Post(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK - assertResult(Map(createdPolicy.email -> Seq(SyncReportItem("added", defaultUserProxyEmail.value.toLowerCase, None)))) { + assertResult(Map(createdPolicy.email -> Seq(SyncReportItem("added", TestSupport.proxyEmail(user.id).value.toLowerCase, None)))) { responseAs[Map[WorkbenchEmail, Seq[SyncReportItem]]] } } @@ -204,53 +126,31 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou "GET /api/google/policy/{resourceTypeName}/{resourceId}/{accessPolicyName}/sync" should "200 with sync date and policy email" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - //create user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes) + val header = googleSubjectIdHeaderWithId(user.googleSubjectId.get) + val getRoutes = genSamRoutes(samDep) - Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { + Post(s"/api/resource/${resourceType.name}/foo").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent assertResult("") { responseAs[String] } } - Get(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Get(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent assertResult("") { responseAs[String] } } - Post(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Post(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK } - directoryDAO.createGroup(BasicWorkbenchGroup(WorkbenchGroupName(resourceType.ownerRoleName.value + ".foo." + resourceType.name), Set.empty, WorkbenchEmail("foo@bar.com"))) + samDep.directoryDAO.createGroup(BasicWorkbenchGroup(WorkbenchGroupName(resourceType.ownerRoleName.value + ".foo." + resourceType.name), Set.empty, WorkbenchEmail("foo@bar.com"))) - Get(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Get(s"/api/google/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK responseAs[String] should not be empty } @@ -258,41 +158,21 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou "GET /api/google/user/petServiceAccount/{project}/key" should "200 with a new key" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) + val (googleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - //create user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes, Some(googleIamDAO)) + val header = googleSubjectIdHeaderWithId(user.googleSubjectId.get) + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // create a pet service account key - Get("/api/google/user/petServiceAccount/myproject/key") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject/key").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[String] response shouldEqual(expectedJson) @@ -301,53 +181,118 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou "DELETE /api/google/user/petServiceAccount/{project}/key/{keyId}" should "204 when deleting a key" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) + val (googleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - //create user - Post("/register/user") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes, Some(googleIamDAO)) + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // create a pet service account key - Get("/api/google/user/petServiceAccount/myproject/key") ~> samRoutes.route ~> check { + Get("/api/google/user/petServiceAccount/myproject/key").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[String] response shouldEqual(expectedJson) } // create a pet service account key - Delete("/api/google/user/petServiceAccount/myproject/key/123") ~> samRoutes.route ~> check { + Delete("/api/google/user/petServiceAccount/myproject/key/123").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } } - private def createMockGoogleIamDaoForSAKeyTests: (GoogleIamDAO, String) = { + "GET /api/google/petServiceAccount/{project}/{userEmail}" should "200 with a key" in { + val (defaultUserInfo, samRoutes, expectedJson, headers) = setupPetSATest() + + val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) + Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members).withHeaders(headers) ~> samRoutes.route ~> check { + status shouldEqual StatusCodes.Created + } + + // create a pet service account key + Get(s"/api/google/petServiceAccount/myproject/${defaultUserInfo.userEmail.value}").withHeaders(headers) ~> samRoutes.route ~> check { + status shouldEqual StatusCodes.OK + val response = responseAs[String] + response shouldEqual(expectedJson) + } + } + + it should "404 when user does not exist" in { + val (defaultUserInfo, samRoutes, _, headers) = setupPetSATest() + + val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) + Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members).withHeaders(headers) ~> samRoutes.route ~> check { + status shouldEqual StatusCodes.Created + } + + // create a pet service account key + Get(s"/api/google/petServiceAccount/myproject/I-do-not-exist@foo.bar").withHeaders(headers) ~> samRoutes.route ~> check { + status shouldEqual StatusCodes.NotFound + } + } + + it should "403 when caller does not have action" in { + val (_, samRoutes, _, headers) = setupPetSATest() + + // create a pet service account key + Get(s"/api/google/petServiceAccount/myproject/I-do-not-exist@foo.bar").withHeaders(headers) ~> samRoutes.route ~> check { + status shouldEqual StatusCodes.Forbidden + } + } +} + +trait GoogleExtensionRoutesSpecHelper extends FlatSpec with Matchers with ScalatestRouteTest with TestSupport with MockitoSugar{ + val defaultUserId = WorkbenchUserId("newuser123") + val defaultUserEmail = WorkbenchEmail("newuser@new.com") + /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 + val defaultUserProxyEmail = WorkbenchEmail(s"newuser_$defaultUserId@${googleServicesConfig.appsDomain}") + */ + val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_$defaultUserId@${googleServicesConfig.appsDomain}") + /**/ + + lazy val config = ConfigFactory.load() + lazy val petServiceAccountConfig = config.as[PetServiceAccountConfig]("petServiceAccount") + lazy val googleServicesConfig = config.as[GoogleServicesConfig]("googleServices") + + val configResourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.map(rt => rt.name -> rt).toMap + + def createTestUser(resourceTypes: Map[ResourceTypeName, ResourceType] = Map.empty[ResourceTypeName, ResourceType], + googIamDAO: Option[GoogleIamDAO] = None, + googleServicesConfig: GoogleServicesConfig = TestSupport.googleServicesConfig, + googSubjectId: Option[GoogleSubjectId] = None): (WorkbenchUser, List[RawHeader], SamDependencies) = { + val googleSubjectId = googSubjectId.map(_.value).getOrElse(genRandom(System.currentTimeMillis())) + val googHeader = RawHeader(api.googleSubjectIdHeader, googleSubjectId) + val emailHeader = RawHeader(api.emailHeader, defaultUserEmail.value) + + val samDependencies = genSamDependencies(resourceTypes, googIamDAO, googleServicesConfig) + val createRoutes = genSamRoutes(samDependencies) + + // create a user + val user = Post("/register/user/v1/").withHeaders(googHeader, emailHeader) ~> createRoutes.route ~> check { + status shouldEqual StatusCodes.Created + val res = responseAs[UserStatus] + res.userInfo.userEmail shouldBe defaultUserEmail + res.enabled shouldBe Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true) + + WorkbenchUser(res.userInfo.userSubjectId, Some(GoogleSubjectId(googleSubjectId)), res.userInfo.userEmail) + } + + val headers = List( + RawHeader(api.emailHeader, user.email.value), + RawHeader(api.userIdHeader, user.id.value), + TestSupport.googleSubjectIdHeaderWithId(user.googleSubjectId.get), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + (user, headers, samDependencies) + } + + def createMockGoogleIamDaoForSAKeyTests: (GoogleIamDAO, String) = { val googleIamDAO = mock[GoogleIamDAO] val expectedJson = """{"json":"yes I am"}""" when(googleIamDAO.findServiceAccount(any[GoogleProject], any[ServiceAccountName])).thenReturn(Future.successful(None)) @@ -358,8 +303,10 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou (googleIamDAO, expectedJson) } - private def setupPetSATest: (UserInfo, TestSamRoutes with GoogleExtensionRoutes, String) = { - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) + def setupPetSATest(): (UserInfo, TestSamRoutes, String, List[RawHeader]) = { + val googleSubjectId = genRandom(System.currentTimeMillis()) + + val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), None, WorkbenchEmail("user1@example.com"), 0) val googleDirectoryDAO = new MockGoogleDirectoryDAO() val directoryDAO = new MockDirectoryDAO() val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests @@ -368,58 +315,58 @@ class GoogleExtensionRoutesSpec extends FlatSpec with Matchers with ScalatestRou val pubSubDAO = new MockGooglePubSubDAO() val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig.copy(serviceAccountClientEmail = defaultUserInfo.userEmail, serviceAccountClientId = defaultUserInfo.userId.value), petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) + val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig.copy(serviceAccountClientEmail = defaultUserInfo.userEmail, serviceAccountClientId = googleSubjectId), petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) val emailDomain = "example.com" val mockResourceService = new ResourceService(configResourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) val userService = new UserService(directoryDAO, googleExt) val statusService = new StatusService(directoryDAO, NoExtensions) val managedGroupService = new ManagedGroupService(mockResourceService, configResourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, userService, statusService, managedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { + + val createRoutes = new TestSamRoutes(mockResourceService, userService, statusService, managedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, None, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { val googleExtensions = googleExt val googleKeyCache = cloudKeyCache } - - runAndWait(googleExt.onBoot(SamApplication(userService, mockResourceService, statusService))) - (defaultUserInfo, samRoutes, expectedJson) - } - - "GET /api/google/petServiceAccount/{project}/{userEmail}" should "200 with a key" in { - val (defaultUserInfo, samRoutes, expectedJson) = setupPetSATest - - val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) - Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members) ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } - - // create a pet service account key - Get(s"/api/google/petServiceAccount/myproject/${defaultUserInfo.userEmail.value}") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.OK - val response = responseAs[String] - response shouldEqual(expectedJson) - } - } - - it should "404 when user does not exist" in { - val (defaultUserInfo, samRoutes, _) = setupPetSATest - - val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) - Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members) ~> samRoutes.route ~> check { + val googleSubjectH = TestSupport.googleSubjectIdHeaderWithId(GoogleSubjectId(googleSubjectId)) + val createHeaders = List( + RawHeader(api.emailHeader, defaultUserInfo.userEmail.value), + googleSubjectH, + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + //create user + val newUserId = Post("/register/user").withHeaders(createHeaders) ~> createRoutes.route ~> check { status shouldEqual StatusCodes.Created + responseAs[UserStatus].userInfo.userSubjectId } - // create a pet service account key - Get(s"/api/google/petServiceAccount/myproject/I-do-not-exist@foo.bar") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.NotFound + val samRoutes = new TestSamRoutes(mockResourceService, userService, statusService, managedGroupService, UserInfo(OAuth2BearerToken(""), newUserId, None, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { + val googleExtensions = googleExt + val googleKeyCache = cloudKeyCache } - } - it should "403 when caller does not have action" in { - val (_, samRoutes, _) = setupPetSATest + runAndWait(googleExt.onBoot(SamApplication(userService, mockResourceService, statusService))) - // create a pet service account key - Get(s"/api/google/petServiceAccount/myproject/I-do-not-exist@foo.bar") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Forbidden - } + val headers = List( + RawHeader(api.emailHeader, defaultUserInfo.userEmail.value), + RawHeader(api.userIdHeader, newUserId.value), + TestSupport.googleSubjectIdHeaderWithId(GoogleSubjectId(googleSubjectId)), + RawHeader(api.accessTokenHeader, ""), + RawHeader(api.expiresInHeader, "1000") + ) + (defaultUserInfo.copy(userId = newUserId), samRoutes, expectedJson, headers) + + // //TODO: replace the above code + //// val (googleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests + //// val userInfo = UserInfo(OAuth2BearerToken("accessToken"), defaultUserId, WorkbenchEmail(s"${defaultUserId.value}@example.com"), 0) + //// val googleSubjectId = GoogleSubjectId(genRandom(System.currentTimeMillis())) + //// val (user, headers, samDep) = createTestUser(configResourceTypes, Some(googleIamDAO), TestSupport.googleServicesConfig.copy(serviceAccountClientEmail = userInfo.userEmail, serviceAccountClientId = googleSubjectId.value), Some(googleSubjectId)) + //// val header = googleSubjectIdHeaderWithId(user.googleSubjectId.get) + //// val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), user.id, user.email, 0) + //// val getRoutes = genSamRoutes(samDep, defaultUserInfo) + //// + //// runAndWait(samDep.cloudExtensions.asInstanceOf[GoogleExtensions].onBoot(SamApplication(samDep.userService, samDep.resourceService, samDep.statusService))) + //// println("111: "+defaultUserInfo.userEmail) + //// (defaultUserInfo, getRoutes, expectedJson, header) } -} +} \ No newline at end of file diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesV1Spec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesV1Spec.scala index c2e29c86bf..9636c1a17d 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesV1Spec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionRoutesV1Spec.scala @@ -1,140 +1,63 @@ -package org.broadinstitute.dsde.workbench.sam.google +package org.broadinstitute.dsde.workbench.sam +package google import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.OAuth2BearerToken -import akka.http.scaladsl.testkit.ScalatestRouteTest -import com.typesafe.config.ConfigFactory -import net.ceedubs.ficus.Ficus._ -import org.broadinstitute.dsde.workbench.dataaccess.PubSubNotificationDAO -import org.broadinstitute.dsde.workbench.google.GoogleIamDAO -import org.broadinstitute.dsde.workbench.google.mock.{MockGoogleDirectoryDAO, MockGoogleIamDAO, MockGooglePubSubDAO, MockGoogleStorageDAO} import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._ import org.broadinstitute.dsde.workbench.model._ -import org.broadinstitute.dsde.workbench.model.google._ -import org.broadinstitute.dsde.workbench.sam.TestSupport -import org.broadinstitute.dsde.workbench.sam.api.TestSamRoutes -import org.broadinstitute.dsde.workbench.sam.config.{GoogleServicesConfig, PetServiceAccountConfig, _} -import org.broadinstitute.dsde.workbench.sam.directory.MockDirectoryDAO +import org.broadinstitute.dsde.workbench.sam.TestSupport._ import org.broadinstitute.dsde.workbench.sam.model.SamJsonSupport._ import org.broadinstitute.dsde.workbench.sam.model._ -import org.broadinstitute.dsde.workbench.sam.openam.MockAccessPolicyDAO import org.broadinstitute.dsde.workbench.sam.service._ -import org.mockito.ArgumentMatchers._ -import org.mockito.Mockito._ -import org.scalatest.mockito.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} - -import scala.concurrent.Future /** * Unit tests of GoogleExtensionRoutes. Can use real Google services. Must mock everything else. */ -class GoogleExtensionRoutesV1Spec extends FlatSpec with Matchers with ScalatestRouteTest with TestSupport with MockitoSugar { - val defaultUserId = WorkbenchUserId("newuser123") - val defaultUserEmail = WorkbenchEmail("newuser@new.com") -/* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 - val defaultUserProxyEmail = WorkbenchEmail(s"newuser_$defaultUserId@${googleServicesConfig.appsDomain}") -*/ - val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_$defaultUserId@${googleServicesConfig.appsDomain}") -/**/ - - lazy val config = ConfigFactory.load() - lazy val petServiceAccountConfig = config.as[PetServiceAccountConfig]("petServiceAccount") - lazy val googleServicesConfig = config.as[GoogleServicesConfig]("googleServices") - - val configResourceTypes = config.as[Map[String, ResourceType]]("resourceTypes").values.map(rt => rt.name -> rt).toMap - - def withDefaultRoutes[T](testCode: TestSamRoutes => T): T = { - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, null, googleDirectoryDAO, null, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val samRoutes = new TestSamRoutes(null, new UserService(directoryDAO, googleExt), new StatusService(directoryDAO, NoExtensions), null, UserInfo(OAuth2BearerToken(""), defaultUserId, defaultUserEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - } - testCode(samRoutes) - } - - "GET /api/google/v1/user/petServiceAccount" should "get or create a pet service account for a user" in withDefaultRoutes { samRoutes => - // create a user - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } +class GoogleExtensionRoutesV1Spec extends GoogleExtensionRoutesSpecHelper { + "GET /api/google/v1/user/petServiceAccount" should "get or create a pet service account for a user" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/v1/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // same result a second time - Get("/api/google/v1/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } } - "GET /api/google/v1/user/proxyGroup/{email}" should "return a user's proxy group" in withDefaultRoutes { samRoutes => - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - // create a user - Post("/register/user/v1") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } - - Get(s"/api/google/v1/user/proxyGroup/$defaultUserEmail") ~> samRoutes.route ~> check { + "GET /api/google/v1/user/proxyGroup/{email}" should "return a user's proxy group" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) + Get(s"/api/google/v1/user/proxyGroup/$defaultUserEmail").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] - response shouldBe defaultUserProxyEmail + response shouldBe WorkbenchEmail(s"PROXY_${user.id}@${googleServicesConfig.appsDomain}") } } - it should "return a user's proxy group from a pet service account" in withDefaultRoutes { samRoutes => - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, null, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - // create a user - Post("/register/user/v1") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - responseAs[UserStatus] shouldEqual UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - } + it should "return a user's proxy group from a pet service account" in { + val (user, headers, samDep) = createTestUser() + val getRoutes = genSamRoutes(samDep) - val petEmail = Get("/api/google/v1/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + val petEmail = Get("/api/google/v1/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") response.value } - Get(s"/api/google/v1/user/proxyGroup/$petEmail") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/user/proxyGroup/$petEmail").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] - response shouldBe defaultUserProxyEmail + response shouldBe WorkbenchEmail(s"PROXY_${user.id.value}@${googleServicesConfig.appsDomain}") } } @@ -152,51 +75,30 @@ class GoogleExtensionRoutesV1Spec extends FlatSpec with Matchers with ScalatestR "POST /api/google/v1/policy/{resourceTypeName}/{resourceId}/{accessPolicyName}/sync" should "204 Create Google group for policy" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user123"), WorkbenchEmail("user1@example.com"), 0) +// val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user123"), WorkbenchEmail("user1@example.com"), 0) /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 val defaultUserProxyEmail = WorkbenchEmail(s"user1_user123@${googleServicesConfig.appsDomain}") */ - val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_user123@${googleServicesConfig.appsDomain}") /**/ - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - //create user - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes) + val getRoutes = genSamRoutes(samDep) - Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { + Post(s"/api/resource/${resourceType.name}/foo").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } import spray.json.DefaultJsonProtocol._ - val createdPolicy = Get(s"/api/resource/${resourceType.name}/foo/policies") ~> samRoutes.route ~> check { + val createdPolicy = Get(s"/api/resource/${resourceType.name}/foo/policies").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK responseAs[Seq[AccessPolicyResponseEntry]].find(_.policyName == AccessPolicyName(resourceType.ownerRoleName.value)).getOrElse(fail("created policy not returned by get request")) } import GoogleModelJsonSupport._ - Post(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Post(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK - assertResult(Map(createdPolicy.email -> Seq(SyncReportItem("added", defaultUserProxyEmail.value.toLowerCase, None)))) { + val proxyEmail = WorkbenchEmail(s"PROXY_${user.id}@${googleServicesConfig.appsDomain}") + assertResult(Map(createdPolicy.email -> Seq(SyncReportItem("added", proxyEmail.value.toLowerCase, None)))) { responseAs[Map[WorkbenchEmail, Seq[SyncReportItem]]] } } @@ -204,53 +106,30 @@ class GoogleExtensionRoutesV1Spec extends FlatSpec with Matchers with ScalatestR "GET /api/google/v1/policy/{resourceTypeName}/{resourceId}/{accessPolicyName}/sync" should "200 with sync date and policy email" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val googleIamDAO = new MockGoogleIamDAO() - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - //create user - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes) + val getRoutes = genSamRoutes(samDep) - Post(s"/api/resource/${resourceType.name}/foo") ~> samRoutes.route ~> check { + Post(s"/api/resource/${resourceType.name}/foo").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent assertResult("") { responseAs[String] } } - Get(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent assertResult("") { responseAs[String] } } - Post(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Post(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK } - directoryDAO.createGroup(BasicWorkbenchGroup(WorkbenchGroupName(resourceType.ownerRoleName.value + ".foo." + resourceType.name), Set.empty, WorkbenchEmail("foo@bar.com"))) + samDep.directoryDAO.createGroup(BasicWorkbenchGroup(WorkbenchGroupName(resourceType.ownerRoleName.value + ".foo." + resourceType.name), Set.empty, WorkbenchEmail("foo@bar.com"))) - Get(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/resource/${resourceType.name}/foo/${resourceType.ownerRoleName.value}/sync").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK responseAs[String] should not be empty } @@ -258,142 +137,64 @@ class GoogleExtensionRoutesV1Spec extends FlatSpec with Matchers with ScalatestR "GET /api/google/v1/user/petServiceAccount/{project}/key" should "200 with a new key" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } + val (googleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - //create user - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes, Some(googleIamDAO)) + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/v1/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // create a pet service account key - Get("/api/google/v1/user/petServiceAccount/myproject/key") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject/key").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[String] response shouldEqual(expectedJson) } } + "DELETE /api/google/v1/user/petServiceAccount/{project}/key/{keyId}" should "204 when deleting a key" in { val resourceTypes = Map(resourceType.name -> resourceType) - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val googleStorageDAO = new MockGoogleStorageDAO() - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig, petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val mockUserService = new UserService(directoryDAO, googleExt) - val mockStatusService = new StatusService(directoryDAO, NoExtensions) - val mockManagedGroupService = new ManagedGroupService(mockResourceService, resourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, mockUserService, mockStatusService, mockManagedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } + val (googleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - //create user - Post("/register/user/v1/") ~> samRoutes.route ~> check { - status shouldEqual StatusCodes.Created - } + val (user, headers, samDep) = createTestUser(resourceTypes, Some(googleIamDAO)) + val getRoutes = genSamRoutes(samDep) // create a pet service account - Get("/api/google/v1/user/petServiceAccount/myproject") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[WorkbenchEmail] response.value should endWith (s"@myproject.iam.gserviceaccount.com") } // create a pet service account key - Get("/api/google/v1/user/petServiceAccount/myproject/key") ~> samRoutes.route ~> check { + Get("/api/google/v1/user/petServiceAccount/myproject/key").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[String] response shouldEqual(expectedJson) } // create a pet service account key - Delete("/api/google/v1/user/petServiceAccount/myproject/key/123") ~> samRoutes.route ~> check { + Delete("/api/google/v1/user/petServiceAccount/myproject/key/123").withHeaders(headers) ~> getRoutes.route ~> check { status shouldEqual StatusCodes.NoContent } } - private def createMockGoogleIamDaoForSAKeyTests: (GoogleIamDAO, String) = { - val googleIamDAO = mock[GoogleIamDAO] - val expectedJson = """{"json":"yes I am"}""" - when(googleIamDAO.findServiceAccount(any[GoogleProject], any[ServiceAccountName])).thenReturn(Future.successful(None)) - when(googleIamDAO.createServiceAccount(any[GoogleProject], any[ServiceAccountName], any[ServiceAccountDisplayName])).thenReturn(Future.successful(ServiceAccount(ServiceAccountSubjectId("12312341234"), WorkbenchEmail("pet@myproject.iam.gserviceaccount.com"), ServiceAccountDisplayName("")))) - when(googleIamDAO.createServiceAccountKey(any[GoogleProject], any[WorkbenchEmail])).thenReturn(Future.successful(ServiceAccountKey(ServiceAccountKeyId("foo"), ServiceAccountPrivateKeyData(ServiceAccountPrivateKeyData(expectedJson).encode), None, None))) - when(googleIamDAO.removeServiceAccountKey(any[GoogleProject], any[WorkbenchEmail], any[ServiceAccountKeyId])).thenReturn(Future.successful(())) - when(googleIamDAO.listUserManagedServiceAccountKeys(any[GoogleProject], any[WorkbenchEmail])).thenReturn(Future.successful(Seq.empty)) - (googleIamDAO, expectedJson) - } - - private def setupPetSATest: (UserInfo, TestSamRoutes with GoogleExtensionRoutes, String) = { - val defaultUserInfo = UserInfo(OAuth2BearerToken("accessToken"), WorkbenchUserId("user1"), WorkbenchEmail("user1@example.com"), 0) - val googleDirectoryDAO = new MockGoogleDirectoryDAO() - val directoryDAO = new MockDirectoryDAO() - val (googleIamDAO: GoogleIamDAO, expectedJson: String) = createMockGoogleIamDaoForSAKeyTests - val googleStorageDAO = new MockGoogleStorageDAO - val policyDAO = new MockAccessPolicyDAO() - val pubSubDAO = new MockGooglePubSubDAO() - val notificationDAO = new PubSubNotificationDAO(pubSubDAO, "foo") - val cloudKeyCache = new GoogleKeyCache(googleIamDAO, googleStorageDAO, pubSubDAO, googleServicesConfig, petServiceAccountConfig) - val googleExt = new GoogleExtensions(directoryDAO, policyDAO, googleDirectoryDAO, pubSubDAO, googleIamDAO, googleStorageDAO, null, cloudKeyCache, notificationDAO, googleServicesConfig.copy(serviceAccountClientEmail = defaultUserInfo.userEmail, serviceAccountClientId = defaultUserInfo.userId.value), petServiceAccountConfig, configResourceTypes(CloudExtensions.resourceTypeName)) - - val emailDomain = "example.com" - val mockResourceService = new ResourceService(configResourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val userService = new UserService(directoryDAO, googleExt) - val statusService = new StatusService(directoryDAO, NoExtensions) - val managedGroupService = new ManagedGroupService(mockResourceService, configResourceTypes, policyDAO, directoryDAO, googleExt, emailDomain) - val samRoutes = new TestSamRoutes(mockResourceService, userService, statusService, managedGroupService, UserInfo(OAuth2BearerToken(""), defaultUserInfo.userId, defaultUserInfo.userEmail, 0), directoryDAO) with GoogleExtensionRoutes { - val googleExtensions = googleExt - val googleKeyCache = cloudKeyCache - } - - runAndWait(googleExt.onBoot(SamApplication(userService, mockResourceService, statusService))) - (defaultUserInfo, samRoutes, expectedJson) - } - "GET /api/google/v1/petServiceAccount/{project}/{userEmail}" should "200 with a key" in { - val (defaultUserInfo, samRoutes, expectedJson) = setupPetSATest + val (defaultUserInfo, samRoutes, expectedJson, headers) = setupPetSATest() val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) - Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members) ~> samRoutes.route ~> check { + Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members).withHeaders(headers) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Created } // create a pet service account key - Get(s"/api/google/v1/petServiceAccount/myproject/${defaultUserInfo.userEmail.value}") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/petServiceAccount/myproject/${defaultUserInfo.userEmail.value}").withHeaders(headers) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.OK val response = responseAs[String] response shouldEqual(expectedJson) @@ -401,24 +202,24 @@ class GoogleExtensionRoutesV1Spec extends FlatSpec with Matchers with ScalatestR } it should "404 when user does not exist" in { - val (defaultUserInfo, samRoutes, _) = setupPetSATest + val (defaultUserInfo, samRoutes, _, header) = setupPetSATest() val members = AccessPolicyMembership(Set(defaultUserInfo.userEmail), Set(GoogleExtensions.getPetPrivateKeyAction), Set.empty) - Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members) ~> samRoutes.route ~> check { + Put(s"/api/resource/${CloudExtensions.resourceTypeName.value}/${GoogleExtensions.resourceId.value}/policies/foo", members).withHeaders(genGoogleSubjectIdHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Created } // create a pet service account key - Get(s"/api/google/v1/petServiceAccount/myproject/I-do-not-exist@foo.bar") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/petServiceAccount/myproject/I-do-not-exist@foo.bar").withHeaders(genGoogleSubjectIdHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.NotFound } } it should "403 when caller does not have action" in { - val (_, samRoutes, _) = setupPetSATest + val (_, samRoutes, _, header) = setupPetSATest() // create a pet service account key - Get(s"/api/google/v1/petServiceAccount/myproject/I-do-not-exist@foo.bar") ~> samRoutes.route ~> check { + Get(s"/api/google/v1/petServiceAccount/myproject/I-do-not-exist@foo.bar").withHeaders(genGoogleSubjectIdHeader) ~> samRoutes.route ~> check { status shouldEqual StatusCodes.Forbidden } } diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionSpec.scala index af6d748851..bba92c10f9 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/google/GoogleExtensionSpec.scala @@ -184,12 +184,13 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl } "GoogleExtension" should "get a pet service account for a user" in { - val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, defaultUser: WorkbenchUser) = initPetTest + val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, createDefaultUser: CreateWorkbenchUserAPI) = initPetTest // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + val defaultUser = WorkbenchUser(createDefaultUser.id, Some(GoogleSubjectId("")), createDefaultUser.email) // create a pet service account val googleProject = GoogleProject("testproject") val petServiceAccount = googleExtensions.createUserPetServiceAccount(defaultUser, googleProject).futureValue @@ -232,7 +233,7 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl } - private def initPetTest: (DirectoryDAO, MockGoogleIamDAO, MockGoogleDirectoryDAO, GoogleExtensions, UserService, WorkbenchUserId, WorkbenchEmail, WorkbenchEmail, WorkbenchUser) = { + private def initPetTest: (DirectoryDAO, MockGoogleIamDAO, MockGoogleDirectoryDAO, GoogleExtensions, UserService, WorkbenchUserId, WorkbenchEmail, WorkbenchEmail, CreateWorkbenchUserAPI) = { val dirURI = new URI(directoryConfig.directoryUrl) val connectionPool = new LDAPConnectionPool(new LDAPConnection(dirURI.getHost, dirURI.getPort, directoryConfig.user, directoryConfig.password), directoryConfig.connectionPoolSize) val dirDAO = new LdapDirectoryDAO(connectionPool, directoryConfig) @@ -255,19 +256,20 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl */ val defaultUserProxyEmail = WorkbenchEmail(s"PROXY_newuser123@${googleServicesConfig.appsDomain}") /**/ - val defaultUser = WorkbenchUser(defaultUserId, defaultUserEmail) + val defaultUser = CreateWorkbenchUserAPI(defaultUserId, GoogleSubjectId(""), defaultUserEmail) (dirDAO, mockGoogleIamDAO, mockGoogleDirectoryDAO, googleExtensions, service, defaultUserId, defaultUserEmail, defaultUserProxyEmail, defaultUser) } it should "attach existing service account to pet" in { - val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, defaultUser: WorkbenchUser) = initPetTest + val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, createDefaultUser: CreateWorkbenchUserAPI) = initPetTest val googleProject = GoogleProject("testproject") + val defaultUser = WorkbenchUser(createDefaultUser.id, None, createDefaultUser.email) val (saName, saDisplayName) = googleExtensions.toPetSAFromUser(defaultUser) val serviceAccount = mockGoogleIamDAO.createServiceAccount(googleProject, saName, saDisplayName).futureValue // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) // create a pet service account @@ -277,12 +279,13 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl } it should "recreate service account when missing for pet" in { - val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, defaultUser: WorkbenchUser) = initPetTest + val (dirDAO: DirectoryDAO, mockGoogleIamDAO: MockGoogleIamDAO, mockGoogleDirectoryDAO: MockGoogleDirectoryDAO, googleExtensions: GoogleExtensions, service: UserService, defaultUserId: WorkbenchUserId, defaultUserEmail: WorkbenchEmail, defaultUserProxyEmail: WorkbenchEmail, createDefaultUser: CreateWorkbenchUserAPI) = initPetTest // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) + val defaultUser = WorkbenchUser(createDefaultUser.id, None, createDefaultUser.email) // create a pet service account val googleProject = GoogleProject("testproject") val petServiceAccount = googleExtensions.createUserPetServiceAccount(defaultUser, googleProject).futureValue @@ -425,15 +428,14 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl runAndWait(ge.onBoot(app)) - runAndWait(mockDirectoryDAO.loadUser(WorkbenchUserId(googleServicesConfig.serviceAccountClientId))) shouldBe Some(WorkbenchUser(WorkbenchUserId(googleServicesConfig.serviceAccountClientId), googleServicesConfig.serviceAccountClientEmail)) - runAndWait(mockAccessPolicyDAO.loadPolicy(resourceAndPolicyName)).map(_.copy(email = null)) shouldBe Some(AccessPolicy( - resourceAndPolicyName, - Set(WorkbenchUserId(googleServicesConfig.serviceAccountClientId)), - null, - Set(ResourceRoleName("owner")), - Set.empty - )) - + val uid = runAndWait(mockDirectoryDAO.loadSubjectFromGoogleSubjectId(GoogleSubjectId(googleServicesConfig.serviceAccountClientId))).get.asInstanceOf[WorkbenchUserId] + val owner = runAndWait(mockDirectoryDAO.loadUser(uid)).get + owner.googleSubjectId shouldBe Some(GoogleSubjectId(googleServicesConfig.serviceAccountClientId)) + owner.email shouldBe googleServicesConfig.serviceAccountClientEmail + val res = runAndWait(mockAccessPolicyDAO.loadPolicy(resourceAndPolicyName)).get + res.id shouldBe resourceAndPolicyName + res.members shouldBe Set(owner.id) + res.roles shouldBe Set(ResourceRoleName("owner")) // make sure a repeated call does not fail runAndWait(ge.onBoot(app)) @@ -447,9 +449,9 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl val config = googleServicesConfig.copy(appsDomain = appsDomain) val googleExtensions = new GoogleExtensions(null, null, null, null, null, null, null, null, null, config, null, null ) - val user = WorkbenchUser(WorkbenchUserId(subjectId), WorkbenchEmail(s"$username@test.org")) + val user = WorkbenchUser(WorkbenchUserId(subjectId), None, WorkbenchEmail(s"$username@test.org")) - val proxyEmail = googleExtensions.toProxyFromUser(user).value + val proxyEmail = googleExtensions.toProxyFromUser(user.id).value /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 proxyEmail shouldBe "foo_0123456789@test.cloudfire.org" proxyEmail should include (username) @@ -464,9 +466,9 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl val config = googleServicesConfig.copy(appsDomain = "test.cloudfire.org") val googleExtensions = new GoogleExtensions(null, null, null, null, null, null, null, null, null, config, null, null) - val user = WorkbenchUser(WorkbenchUserId("0123456789"), WorkbenchEmail("foo-bar-baz-qux-quux-corge-grault-garply@test.org")) + val user = WorkbenchUser(WorkbenchUserId("0123456789"), None, WorkbenchEmail("foo-bar-baz-qux-quux-corge-grault-garply@test.org")) - val proxyEmail = googleExtensions.toProxyFromUser(user).value + val proxyEmail = googleExtensions.toProxyFromUser(user.id).value /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 proxyEmail shouldBe "foo-bar-baz-qux-quux-corge-grault-_0123456789@test.cloudfire.org" proxyEmail should have length 64 @@ -478,7 +480,7 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl it should "do Googley stuff onUserCreate" in { val userId = WorkbenchUserId(UUID.randomUUID().toString) val userEmail = WorkbenchEmail("foo@test.org") - val user = WorkbenchUser(userId, userEmail) + val user = WorkbenchUser(userId, None, userEmail) /* Re-enable this code and remove the temporary code below after fixing rawls for GAWB-2933 val proxyEmail = WorkbenchEmail(s"foo_$userId@${googleServicesConfig.appsDomain}") */ @@ -553,10 +555,11 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl val defaultUserId = WorkbenchUserId("newuser") val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val defaultUser = WorkbenchUser(defaultUserId, defaultUserEmail) + val createDefaultUser = CreateWorkbenchUserAPI(defaultUserId, GoogleSubjectId("googleSubjectId1"), defaultUserEmail) + val defaultUser = WorkbenchUser(defaultUserId, None, defaultUserEmail) // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) // create a pet service account @@ -578,10 +581,11 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl val defaultUserId = WorkbenchUserId("newuser") val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val defaultUser = WorkbenchUser(defaultUserId, defaultUserEmail) + val createDefaultUser = CreateWorkbenchUserAPI(defaultUserId, GoogleSubjectId("googleSubjectId1"), defaultUserEmail) + val defaultUser = WorkbenchUser(defaultUserId, None, defaultUserEmail) // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) // create a pet service account @@ -611,10 +615,11 @@ class GoogleExtensionSpec(_system: ActorSystem) extends TestKit(_system) with Fl val defaultUserId = WorkbenchUserId("newuser") val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val defaultUser = WorkbenchUser(defaultUserId, defaultUserEmail) + val createDefaultUser = CreateWorkbenchUserAPI(defaultUserId, GoogleSubjectId("googleSubjectId1"), defaultUserEmail) + val defaultUser = WorkbenchUser(defaultUserId, None, defaultUserEmail) // create a user - val newUser = service.createUser(defaultUser).futureValue + val newUser = service.createUser(createDefaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) // create a pet service account diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/openam/MockAccessPolicyDAOSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/openam/MockAccessPolicyDAOSpec.scala index 245ef39daf..46418f7878 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/openam/MockAccessPolicyDAOSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/openam/MockAccessPolicyDAOSpec.scala @@ -28,7 +28,7 @@ class MockAccessPolicyDAOSpec extends FlatSpec with Matchers with TestSupport wi val schemaLockConfig = ConfigFactory.load().as[SchemaLockConfig]("schemaLock") val schemaDao = new JndiSchemaDAO(directoryConfig, schemaLockConfig) - private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), WorkbenchEmail("user@company.com"), 0) + private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), None, WorkbenchEmail("user@company.com"), 0) override protected def beforeAll(): Unit = { super.beforeAll() @@ -84,7 +84,7 @@ class MockAccessPolicyDAOSpec extends FlatSpec with Matchers with TestSupport wi val jndi = jndiServicesFixture val mock = mockServicesFixture - val dummyUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val dummyUser = CreateWorkbenchUserAPI(dummyUserInfo.userId, GoogleSubjectId("googleSubjectId1"), dummyUserInfo.userEmail) runAndWait(jndi.userService.createUser(dummyUser)) runAndWait(mock.userService.createUser(dummyUser)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ManagedGroupServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ManagedGroupServiceSpec.scala index b200e61ac6..45aef299e9 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ManagedGroupServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ManagedGroupServiceSpec.scala @@ -52,7 +52,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi private val resourceService = new ResourceService(resourceTypeMap, policyDAO, dirDAO, NoExtensions, testDomain) private val managedGroupService = new ManagedGroupService(resourceService, resourceTypeMap, policyDAO, dirDAO, NoExtensions, testDomain) - private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), WorkbenchEmail("user@company.com"), 0) + private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), None, WorkbenchEmail("user@company.com"), 0) override protected def beforeAll(): Unit = { super.beforeAll() @@ -85,7 +85,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi before { runAndWait(schemaDao.clearDatabase()) runAndWait(schemaDao.createOrgUnits()) - runAndWait(dirDAO.createUser(WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail))) + runAndWait(dirDAO.createUser(WorkbenchUser(dummyUserInfo.userId, Some(GoogleSubjectId("asdf")), dummyUserInfo.userEmail))) } "ManagedGroupService create" should "create a managed group with admin and member policies" in { @@ -208,8 +208,8 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi } "ManagedGroupService.overwritePolicyMemberEmails" should "permit overwriting the admin policy" in { - val dummyAdmin = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) - val otherAdmin = WorkbenchUser(WorkbenchUserId("admin2"), WorkbenchEmail("admin2@foo.test")) + val dummyAdmin = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) + val otherAdmin = WorkbenchUser(WorkbenchUserId("admin2"), None, WorkbenchEmail("admin2@foo.test")) val someGroupEmail = WorkbenchEmail("someGroup@some.org") runAndWait(dirDAO.createUser(otherAdmin)) val managedGroup = assertMakeGroup() @@ -230,7 +230,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi it should "throw an exception if any of the email addresses do not match an existing subject" in { val managedGroup = assertMakeGroup() - val badAdmin = WorkbenchUser(WorkbenchUserId("admin2"), WorkbenchEmail("admin2@foo.test")) + val badAdmin = WorkbenchUser(WorkbenchUserId("admin2"), None, WorkbenchEmail("admin2@foo.test")) intercept[WorkbenchExceptionWithErrorReport] { runAndWait(managedGroupService.overwritePolicyMemberEmails(managedGroup.resourceId, ManagedGroupService.adminPolicyName, Set(badAdmin.email))) @@ -240,7 +240,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi it should "permit overwriting the member policy" in { val managedGroup = assertMakeGroup() - val someUser = WorkbenchUser(WorkbenchUserId("someUser"), WorkbenchEmail("someUser@foo.test")) + val someUser = WorkbenchUser(WorkbenchUserId("someUser"), None, WorkbenchEmail("someUser@foo.test")) val someGroupEmail = WorkbenchEmail("someGroup@some.org") runAndWait(dirDAO.createUser(someUser)) runAndWait(dirDAO.createGroup(BasicWorkbenchGroup(WorkbenchGroupName("someGroup"), Set.empty, someGroupEmail))) @@ -254,13 +254,13 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi } "ManagedGroupService addSubjectToPolicy" should "successfully add the subject to the existing policy for the group" in { - val adminUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val adminUser = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) val managedGroup = assertMakeGroup() runAndWait(managedGroupService.listPolicyMemberEmails(managedGroup.resourceId, ManagedGroupService.adminPolicyName)) shouldEqual Set(adminUser.email) - val someUser = WorkbenchUser(WorkbenchUserId("someUser"), WorkbenchEmail("someUser@foo.test")) + val someUser = WorkbenchUser(WorkbenchUserId("someUser"), None, WorkbenchEmail("someUser@foo.test")) runAndWait(dirDAO.createUser(someUser)) runAndWait(managedGroupService.addSubjectToPolicy(managedGroup.resourceId, ManagedGroupService.adminPolicyName, someUser.id)) @@ -269,7 +269,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi } it should "succeed without changing if the email address is already in the policy" in { - val adminUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val adminUser = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) val managedGroup = assertMakeGroup() @@ -281,18 +281,18 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi // TODO: Is this right? ResourceService.overwriteResource fails with invalid emails, should addSubjectToPolicy fail too? // The correct behavior is enforced in the routing, but is that the right place? Should it be enforced in the Service class? it should "succeed even if the subject is doesn't exist" in { - val adminUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val adminUser = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) val managedGroup = assertMakeGroup() runAndWait(managedGroupService.listPolicyMemberEmails(managedGroup.resourceId, ManagedGroupService.adminPolicyName)) shouldEqual Set(adminUser.email) - val someUser = WorkbenchUser(WorkbenchUserId("someUser"), WorkbenchEmail("someUser@foo.test")) + val someUser = WorkbenchUser(WorkbenchUserId("someUser"), None, WorkbenchEmail("someUser@foo.test")) runAndWait(managedGroupService.addSubjectToPolicy(managedGroup.resourceId, ManagedGroupService.adminPolicyName, someUser.id)) } "ManagedGroupService removeSubjectFromPolicy" should "successfully remove the subject from the policy for the group" in { - val adminUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val adminUser = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) val managedGroup = assertMakeGroup() @@ -304,7 +304,7 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi } it should "not do anything if the subject is not a member of the policy" in { - val adminUser = WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail) + val adminUser = WorkbenchUser(dummyUserInfo.userId, None, dummyUserInfo.userEmail) val managedGroup = assertMakeGroup() @@ -329,10 +329,10 @@ class ManagedGroupServiceSpec extends FlatSpec with Matchers with TestSupport wi val resService = new ResourceService(resTypes, policyDAO, dirDAO, NoExtensions, testDomain) val mgService = new ManagedGroupService(resService, resTypes, policyDAO, dirDAO, NoExtensions, testDomain) - val user1 = UserInfo(OAuth2BearerToken("token1"), WorkbenchUserId("userId1"), WorkbenchEmail("user1@company.com"), 0) - val user2 = UserInfo(OAuth2BearerToken("token2"), WorkbenchUserId("userId2"), WorkbenchEmail("user2@company.com"), 0) - runAndWait(dirDAO.createUser(WorkbenchUser(user1.userId, user1.userEmail))) - runAndWait(dirDAO.createUser(WorkbenchUser(user2.userId, user2.userEmail))) + val user1 = UserInfo(OAuth2BearerToken("token1"), WorkbenchUserId("userId1"), None, WorkbenchEmail("user1@company.com"), 0) + val user2 = UserInfo(OAuth2BearerToken("token2"), WorkbenchUserId("userId2"), None, WorkbenchEmail("user2@company.com"), 0) + runAndWait(dirDAO.createUser(WorkbenchUser(user1.userId, None, user1.userEmail))) + runAndWait(dirDAO.createUser(WorkbenchUser(user2.userId, None, user2.userEmail))) val user1Groups = Set("foo", "bar", "baz") val user2Groups = Set("qux", "quux") diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ResourceServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ResourceServiceSpec.scala index 08b55f204c..d0c86e9123 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ResourceServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/ResourceServiceSpec.scala @@ -1,4 +1,5 @@ -package org.broadinstitute.dsde.workbench.sam.service +package org.broadinstitute.dsde.workbench.sam +package service import java.net.URI import java.util.UUID @@ -32,7 +33,7 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B val policyDAO = new LdapAccessPolicyDAO(connectionPool, directoryConfig) val schemaDao = new JndiSchemaDAO(directoryConfig, schemaLockConfig) - private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), WorkbenchEmail("user@company.com"), 0) + private val dummyUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("userid"), None, WorkbenchEmail("user@company.com"), 0) private val defaultResourceTypeActions = Set(ResourceAction("alter_policies"), ResourceAction("delete"), ResourceAction("read_policies"), ResourceAction("view"), ResourceAction("non_owner_action")) private val defaultResourceTypeActionPatterns = Set(SamResourceActionPatterns.alterPolicies, SamResourceActionPatterns.delete, SamResourceActionPatterns.readPolicies, ResourceActionPattern("view", "", false), ResourceActionPattern("non_owner_action", "", false)) @@ -80,7 +81,7 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B before { runAndWait(schemaDao.clearDatabase()) runAndWait(schemaDao.createOrgUnits()) - runAndWait(dirDAO.createUser(WorkbenchUser(dummyUserInfo.userId, dummyUserInfo.userEmail))) + runAndWait(dirDAO.createUser(WorkbenchUser(dummyUserInfo.userId, Some(GoogleSubjectId(genRandom(System.currentTimeMillis()))), dummyUserInfo.userEmail))) } def toEmail(resourceType: String, resourceName: String, policyName: String) = { @@ -170,7 +171,7 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B it should "list the user's actions for a resource with nested groups" in { val resourceName1 = ResourceId("resource1") - val user = runAndWait(dirDAO.createUser(WorkbenchUser(WorkbenchUserId("asdfawefawea"), WorkbenchEmail("asdfawefawea@foo.bar")))) + val user = runAndWait(dirDAO.createUser(WorkbenchUser(WorkbenchUserId("asdfawefawea"), None, WorkbenchEmail("asdfawefawea@foo.bar")))) val group = BasicWorkbenchGroup(WorkbenchGroupName("g"), Set(user.id), WorkbenchEmail("foo@bar.com")) runAndWait(dirDAO.createGroup(group)) @@ -179,7 +180,7 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B val nonOwnerAction = ResourceAction("non_owner_action") runAndWait(service.overwritePolicy(defaultResourceType, AccessPolicyName("new_policy"), resource, AccessPolicyMembership(Set(group.email), Set(nonOwnerAction), Set.empty))) - val userInfo = UserInfo(OAuth2BearerToken(""), user.id, user.email, 0) + val userInfo = UserInfo(OAuth2BearerToken(""), user.id, None, user.email, 0) assertResult(Set(ResourceAction("non_owner_action"))) { runAndWait(service.listUserResourceActions(Resource(defaultResourceType.name, resourceName1), userInfo)) } @@ -292,8 +293,8 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B constrainableResourceType.isAuthDomainConstrainable shouldEqual true runAndWait(constrainableService.createResourceType(constrainableResourceType)) - val bender = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("Bender"), WorkbenchEmail("bender@planex.com"), 0) - runAndWait(dirDAO.createUser(WorkbenchUser(bender.userId, bender.userEmail))) + val bender = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("Bender"), None, WorkbenchEmail("bender@planex.com"), 0) + runAndWait(dirDAO.createUser(WorkbenchUser(bender.userId, None, bender.userEmail))) runAndWait(constrainableService.createResourceType(managedGroupResourceType)) val managedGroupName1 = "firstGroup" @@ -569,9 +570,9 @@ class ResourceServiceSpec extends FlatSpec with Matchers with TestSupport with B "add/remove SubjectToPolicy" should "add/remove subject and tolerate prior (non)existence" in { val resource = Resource(defaultResourceType.name, ResourceId("my-resource")) val policyName = AccessPolicyName(defaultResourceType.ownerRoleName.value) - val otherUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("otheruserid"), WorkbenchEmail("otheruser@company.com"), 0) + val otherUserInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId("otheruserid"), None, WorkbenchEmail("otheruser@company.com"), 0) - runAndWait(dirDAO.createUser(WorkbenchUser(otherUserInfo.userId, otherUserInfo.userEmail))) + runAndWait(dirDAO.createUser(WorkbenchUser(otherUserInfo.userId, None, otherUserInfo.userEmail))) runAndWait(service.createResourceType(defaultResourceType)) runAndWait(service.createResource(defaultResourceType, resource.resourceId, dummyUserInfo)) diff --git a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/UserServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/UserServiceSpec.scala index 3cfddcc17b..0a72f7aa85 100644 --- a/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/UserServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/workbench/sam/service/UserServiceSpec.scala @@ -1,4 +1,5 @@ -package org.broadinstitute.dsde.workbench.sam.service +package org.broadinstitute.dsde.workbench.sam +package service import java.net.URI import java.util.UUID @@ -12,7 +13,7 @@ import org.broadinstitute.dsde.workbench.sam.TestSupport import org.broadinstitute.dsde.workbench.sam.config.{DirectoryConfig, PetServiceAccountConfig, SchemaLockConfig} import org.broadinstitute.dsde.workbench.sam.google.GoogleExtensions import org.broadinstitute.dsde.workbench.sam.directory.{DirectoryDAO, LdapDirectoryDAO} -import org.broadinstitute.dsde.workbench.sam.model.{BasicWorkbenchGroup, UserStatus, UserStatusDetails, UserStatusInfo, UserStatusDiagnostics} +import org.broadinstitute.dsde.workbench.sam.model.{BasicWorkbenchGroup, UserStatus, UserStatusDetails, UserStatusDiagnostics, UserStatusInfo} import org.broadinstitute.dsde.workbench.sam.schema.JndiSchemaDAO import org.mockito.ArgumentMatchers._ import org.mockito.Mockito._ @@ -32,10 +33,11 @@ class UserServiceSpec extends FlatSpec with Matchers with TestSupport with Mocki override implicit val patienceConfig = PatienceConfig(timeout = scaled(5.seconds)) - val defaultUserId = WorkbenchUserId("newuser") + val defaultUserId = genWorkbenchUserId(System.currentTimeMillis()) + val defaultGoogleSubjectId = GoogleSubjectId("newuser") val defaultUserEmail = WorkbenchEmail("newuser@new.com") - val defaultUser = WorkbenchUser(defaultUserId, defaultUserEmail) - val userInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId(UUID.randomUUID().toString), WorkbenchEmail("user@company.com"), 0) + val defaultUser = CreateWorkbenchUserAPI(defaultUserId, defaultGoogleSubjectId, defaultUserEmail) + val userInfo = UserInfo(OAuth2BearerToken("token"), WorkbenchUserId(UUID.randomUUID().toString), None, WorkbenchEmail("user@company.com"), 0) lazy val config = ConfigFactory.load() lazy val directoryConfig = config.as[DirectoryConfig]("directory") @@ -74,10 +76,10 @@ class UserServiceSpec extends FlatSpec with Matchers with TestSupport with Mocki // create a user val newUser = service.createUser(defaultUser).futureValue newUser shouldBe UserStatus(UserStatusDetails(defaultUserId, defaultUserEmail), Map("ldap" -> true, "allUsersGroup" -> true, "google" -> true)) - verify(googleExtensions).onUserCreate(defaultUser) + verify(googleExtensions).onUserCreate(WorkbenchUser(defaultUser.id, Some(defaultUser.googleSubjectId), defaultUser.email)) // check ldap - dirDAO.loadUser(defaultUserId).futureValue shouldBe Some(defaultUser) + dirDAO.loadUser(defaultUserId).futureValue shouldBe Some(WorkbenchUser(defaultUser.id, Some(defaultUser.googleSubjectId), defaultUser.email)) dirDAO.isEnabled(defaultUserId).futureValue shouldBe true dirDAO.loadGroup(service.cloudExtensions.allUsersGroupName).futureValue shouldBe Some(BasicWorkbenchGroup(service.cloudExtensions.allUsersGroupName, Set(defaultUserId), service.cloudExtensions.getOrCreateAllUsersGroup(dirDAO).futureValue.email))