-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ID-1301 talk to ecm for nih username (Round 2) #1407
Changes from 29 commits
58d4922
73fa847
f31ae71
94f0f0b
4631e01
0649477
62a3f16
08bc3f2
8b18883
ca9e839
361c9f2
5c5598b
10882f5
daf388e
12b4aee
bda6d6d
94a105c
2cfcb16
77ea5a9
5679cec
c9e293c
3c06bfe
c7cc114
fb57e2c
668b525
d896e7d
3cd1230
4aa9735
d6b0555
d07a609
fe7fdbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,10 @@ object Dependencies { | |
val excludeAkkaHttp = ExclusionRule(organization = "com.typesafe.akka", name = "akka-http_2.13") | ||
val excludeSprayJson = ExclusionRule(organization = "com.typesafe.akka", name = "akka-http-spray-json_2.13") | ||
|
||
val excludeSpring = ExclusionRule(organization = "org.springframework") | ||
val excludeSpringBoot = ExclusionRule(organization = "org.springframework.boot") | ||
val excludeSpringJcl = ExclusionRule(organization = "org.springframework", name = "spring-jcl") | ||
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exclusions to resolve |
||
|
||
// Overrides for transitive dependencies. These apply - via Settings.scala - to all projects in this codebase. | ||
// These are overrides only; if the direct dependencies stop including any of these, they will not be included | ||
// by being listed here. | ||
|
@@ -50,9 +54,11 @@ object Dependencies { | |
excludeGuava("org.broadinstitute.dsde.workbench" %% "workbench-util" % s"0.10-$workbenchLibsHash"), | ||
"org.broadinstitute.dsde.workbench" %% "workbench-google2" % s"0.36-$workbenchLibsHash", | ||
"org.broadinstitute.dsde.workbench" %% "workbench-oauth2" % s"0.7-$workbenchLibsHash", | ||
"org.broadinstitute.dsde.workbench" %% "sam-client" % "0.1-ef83073", | ||
"org.broadinstitute.dsde.workbench" %% "sam-client" % "v0.0.263", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was ancient! |
||
"org.broadinstitute.dsde.workbench" %% "workbench-notifications" %s"0.6-$workbenchLibsHash", | ||
"org.databiosphere" % "workspacedataservice-client-okhttp-jakarta" % "0.2.167-SNAPSHOT", | ||
"bio.terra" % "externalcreds-client-resttemplate" % "1.44.0-20240725.201427-1" excludeAll(excludeSpring, excludeSpringBoot), | ||
"org.springframework" % "spring-web" % "6.1.11" excludeAll(excludeSpringBoot, excludeSpringJcl), | ||
|
||
"com.typesafe.akka" %% "akka-actor" % akkaV, | ||
"com.typesafe.akka" %% "akka-slf4j" % akkaV, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package org.broadinstitute.dsde.firecloud.dataaccess | ||
|
||
import com.typesafe.scalalogging.LazyLogging | ||
import org.broadinstitute.dsde.firecloud.model.{LinkedEraAccount, UserInfo, WithAccessToken} | ||
|
||
import scala.concurrent.Future | ||
|
||
class DisabledExternalCredsDAO extends ExternalCredsDAO with LazyLogging { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed so that Orch can run in BEEs. In the future, we'll need to get ECM to run in BEEs so that swat tests can pass without Thurloe. |
||
|
||
override def getLinkedAccount(implicit userInfo: UserInfo): Future[Option[LinkedEraAccount]] = Future.successful { | ||
logger.warn("Getting Linked eRA Account from ECM, but ECM is disabled.") | ||
None | ||
} | ||
|
||
override def putLinkedEraAccount(linkedEraAccount: LinkedEraAccount)(implicit orchInfo: WithAccessToken): Future[Unit] = Future.successful { | ||
logger.warn("Putting Linked eRA Account to ECM, but ECM is disabled.") | ||
} | ||
|
||
override def deleteLinkedEraAccount(userInfo: UserInfo)(implicit orchInfo: WithAccessToken): Future[Unit] = Future.successful { | ||
logger.warn("Deleting Linked eRA Account from ECM, but ECM is disabled.") | ||
} | ||
|
||
override def getLinkedEraAccountForUsername(username: String)(implicit orchInfo: WithAccessToken): Future[Option[LinkedEraAccount]] = Future.successful { | ||
logger.warn("Getting Linked eRA Account for username from ECM, but ECM is disabled.") | ||
None | ||
} | ||
|
||
override def getActiveLinkedEraAccounts(implicit orchInfo: WithAccessToken): Future[Seq[LinkedEraAccount]] = Future.successful { | ||
logger.warn("Getting Active Linked eRA Accounts from ECM, but ECM is disabled.") | ||
Seq.empty | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.broadinstitute.dsde.firecloud.dataaccess | ||
|
||
import org.broadinstitute.dsde.firecloud.model.{LinkedEraAccount, UserInfo, WithAccessToken} | ||
import org.databiosphere.workspacedata.client.ApiException | ||
|
||
import scala.concurrent.Future | ||
|
||
trait ExternalCredsDAO { | ||
|
||
@throws(classOf[ApiException]) | ||
def getLinkedAccount(implicit userInfo: UserInfo): Future[Option[LinkedEraAccount]] | ||
|
||
@throws(classOf[ApiException]) | ||
def putLinkedEraAccount(linkedEraAccount: LinkedEraAccount)(implicit orchInfo: WithAccessToken): Future[Unit] | ||
|
||
@throws(classOf[ApiException]) | ||
def deleteLinkedEraAccount(userInfo: UserInfo)(implicit orchInfo: WithAccessToken): Future[Unit] | ||
|
||
|
||
@throws(classOf[ApiException]) | ||
def getLinkedEraAccountForUsername(username: String)(implicit orchInfo: WithAccessToken): Future[Option[LinkedEraAccount]] | ||
|
||
@throws(classOf[ApiException]) | ||
def getActiveLinkedEraAccounts(implicit orchInfo: WithAccessToken): Future[Seq[LinkedEraAccount]] | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package org.broadinstitute.dsde.firecloud.dataaccess | ||
|
||
import bio.terra.externalcreds.api.OauthApi | ||
import bio.terra.externalcreds.api.AdminApi | ||
import bio.terra.externalcreds.client.ApiClient | ||
import bio.terra.externalcreds.model.Provider | ||
import com.google.api.client.http.HttpStatusCodes | ||
import org.broadinstitute.dsde.firecloud.FireCloudConfig | ||
import org.broadinstitute.dsde.firecloud.model.LinkedEraAccount.unapply | ||
import org.broadinstitute.dsde.firecloud.model.{LinkedEraAccount, UserInfo, WithAccessToken} | ||
import org.broadinstitute.dsde.workbench.model.WorkbenchException | ||
import org.joda.time.DateTime | ||
import org.springframework.web.client.{HttpClientErrorException, RestTemplate} | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
import scala.jdk.CollectionConverters._ | ||
|
||
class HttpExternalCredsDAO(implicit val executionContext: ExecutionContext) extends ExternalCredsDAO { | ||
|
||
private lazy val restTemplate = new RestTemplate | ||
|
||
private def handleError[A](e: HttpClientErrorException, operation: String): Option[A] = { | ||
e.getStatusCode.value() match { | ||
case HttpStatusCodes.STATUS_CODE_NOT_FOUND => None | ||
case _ => throw new WorkbenchException(s"Failed to $operation: ${e.getMessage}") | ||
} | ||
} | ||
|
||
override def getLinkedAccount(implicit userInfo: UserInfo): Future[Option[LinkedEraAccount]] = Future { | ||
val oauthApi: OauthApi = getOauthApi(userInfo.accessToken.token) | ||
try { | ||
val linkInfo = oauthApi.getLink(Provider.ERA_COMMONS) | ||
Some(LinkedEraAccount(userInfo.id, linkInfo.getExternalUserId, new DateTime(linkInfo.getExpirationTimestamp))) | ||
} catch { | ||
case e: HttpClientErrorException => handleError(e, "GET eRA Linked Account") | ||
} | ||
} | ||
|
||
override def putLinkedEraAccount(linkedEraAccount: LinkedEraAccount)(implicit orchInfo: WithAccessToken): Future[Unit] = Future { | ||
val adminApi = getAdminApi(orchInfo.accessToken.token) | ||
adminApi.putLinkedAccountWithFakeToken(unapply(linkedEraAccount), Provider.ERA_COMMONS) | ||
} | ||
|
||
override def deleteLinkedEraAccount(userInfo: UserInfo)(implicit orchInfo: WithAccessToken): Future[Unit] = Future { | ||
val adminApi = getAdminApi(orchInfo.accessToken.token) | ||
adminApi.adminDeleteLinkedAccount(userInfo.id, Provider.ERA_COMMONS) | ||
} | ||
|
||
override def getLinkedEraAccountForUsername(username: String)(implicit orchInfo: WithAccessToken): Future[Option[LinkedEraAccount]] = Future { | ||
val adminApi = getAdminApi(orchInfo.accessToken.token) | ||
try { | ||
val adminLinkInfo = adminApi.getLinkedAccountForExternalId(Provider.ERA_COMMONS, username) | ||
Some(LinkedEraAccount(adminLinkInfo)) | ||
} catch { | ||
case e: HttpClientErrorException => handleError(e, s"GET eRA Linked Account for username [$username]") | ||
} | ||
} | ||
|
||
override def getActiveLinkedEraAccounts(implicit orchInfo: WithAccessToken): Future[Seq[LinkedEraAccount]] = Future { | ||
val adminApi = getAdminApi(orchInfo.accessToken.token) | ||
val adminLinkInfos = adminApi.getActiveLinkedAccounts(Provider.ERA_COMMONS) | ||
adminLinkInfos.asScala.map(LinkedEraAccount.apply).toSeq | ||
} | ||
|
||
private def getApi(accessToken: String): ApiClient = { | ||
val client = new ApiClient(restTemplate) | ||
client.setBasePath(FireCloudConfig.ExternalCreds.baseUrl) | ||
client.setAccessToken(accessToken) | ||
client | ||
} | ||
|
||
private def getOauthApi(accessToken: String): OauthApi = { | ||
val client = getApi(accessToken) | ||
new OauthApi(client) | ||
} | ||
|
||
private def getAdminApi(accessToken: String): AdminApi = { | ||
val client = getApi(accessToken) | ||
new AdminApi(client) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,12 +12,13 @@ import org.broadinstitute.dsde.firecloud.model.ErrorReportExtensions.FCErrorRepo | |
import org.broadinstitute.dsde.firecloud.model.ManagedGroupRoles.ManagedGroupRole | ||
import org.broadinstitute.dsde.firecloud.model.ModelJsonProtocol._ | ||
import org.broadinstitute.dsde.firecloud.model.SamResource.UserPolicy | ||
import org.broadinstitute.dsde.firecloud.model.{AccessToken, FireCloudManagedGroupMembership, ManagedGroupRoles, RegistrationInfo, RegistrationInfoV2, SamUserAttributesRequest, SamUserRegistrationRequest, SamUserResponse, UserIdInfo, UserInfo, WithAccessToken} | ||
import org.broadinstitute.dsde.firecloud.model.{AccessToken, FireCloudManagedGroupMembership, ManagedGroupRoles, RegistrationInfo, RegistrationInfoV2, SamUserAttributesRequest, SamUserRegistrationRequest, SamUserResponse, UserIdInfo, UserInfo, WithAccessToken, WorkbenchUserInfo} | ||
import org.broadinstitute.dsde.firecloud.utils.RestJsonClient | ||
import org.broadinstitute.dsde.rawls.model.RawlsUserEmail | ||
import org.broadinstitute.dsde.workbench.client.sam.model.User | ||
import org.broadinstitute.dsde.workbench.model.WorkbenchIdentityJsonSupport._ | ||
import org.broadinstitute.dsde.workbench.model.google.GoogleProject | ||
import org.broadinstitute.dsde.workbench.model.{WorkbenchEmail, WorkbenchGroupName} | ||
import org.broadinstitute.dsde.workbench.model.{WorkbenchEmail, WorkbenchGroupName, WorkbenchUserId} | ||
import org.broadinstitute.dsde.workbench.util.health.SubsystemStatus | ||
import spray.json.DefaultJsonProtocol._ | ||
import spray.json.{JsValue, JsonFormat, RootJsonFormat} | ||
|
@@ -50,6 +51,15 @@ class HttpSamDAO( implicit val system: ActorSystem, val materializer: Materializ | |
authedRequestToObject[UserIdInfo](Get(samGetUserIdsUrl.format(URLEncoder.encode(email.value, UTF_8.name)))) | ||
} | ||
|
||
|
||
// Sam's API only allows for 1000 user to be fetched at one time | ||
override def getUsersForIds(samUserIds: Seq[WorkbenchUserId])(implicit userInfo: WithAccessToken): Future[Seq[WorkbenchUserInfo]] = Future.sequence { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New Sam API to resolve a list of users from user ids. |
||
samUserIds.sliding(1000, 1000).toSeq.map { batch => | ||
adminAuthedRequestToObject[Seq[SamUserResponse]](Post(samAdminGetUsersForIdsUrl, batch)) | ||
.map(_.map(user => WorkbenchUserInfo(user.id.value, user.email.value))) | ||
} | ||
}.map(_.flatten) | ||
|
||
override def isGroupMember(groupName: WorkbenchGroupName, userInfo: UserInfo): Future[Boolean] = { | ||
implicit val accessToken = userInfo | ||
authedRequestToObject[List[String]](Get(samResourceRoles(managedGroupResourceTypeName, groupName.value)), label=Some("HttpSamDAO.isGroupMember")).map { allRoles => | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.broadinstitute.dsde.firecloud.model | ||
|
||
import bio.terra.externalcreds.model.AdminLinkInfo | ||
import org.joda.time.{DateTime, Instant} | ||
|
||
object LinkedEraAccount { | ||
def apply(samUserId: String, nihLink: NihLink): LinkedEraAccount = { | ||
LinkedEraAccount(samUserId, nihLink.linkedNihUsername, Instant.ofEpochSecond(nihLink.linkExpireTime).toDateTime) | ||
} | ||
|
||
def apply(adminLinkInfo: AdminLinkInfo): LinkedEraAccount = { | ||
LinkedEraAccount(adminLinkInfo.getUserId, adminLinkInfo.getLinkedExternalId, new DateTime(adminLinkInfo.getLinkExpireTime)) | ||
} | ||
|
||
def unapply(linkedEraAccount: LinkedEraAccount): AdminLinkInfo = { | ||
new AdminLinkInfo() | ||
.userId(linkedEraAccount.userId) | ||
.linkedExternalId(linkedEraAccount.linkedExternalId) | ||
.linkExpireTime(linkedEraAccount.linkExpireTime.toDate) | ||
} | ||
} | ||
|
||
case class LinkedEraAccount(userId: String, linkedExternalId: String, linkExpireTime: DateTime) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.broadinstitute.dsde.firecloud.model | ||
|
||
import java.time.Instant | ||
|
||
case class SamUser(id: String, | ||
googleSubjectId: String, | ||
email: String, | ||
azureB2CId: String, | ||
enabled: Boolean, | ||
acceptedTosVersion: String, | ||
createdAt: Instant, | ||
registeredAt: Option[Instant], | ||
updatedAt: Instant) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package org.broadinstitute.dsde.firecloud.model | |
|
||
import akka.http.scaladsl.model.headers.OAuth2BearerToken | ||
|
||
import java.time.OffsetDateTime | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still need this import? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nope! |
||
import scala.util.Try | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes a
error