Skip to content

Commit

Permalink
Merge pull request #38 from Ferlab-Ste-Justine/fix/cqdg-793_cavatica_…
Browse files Browse the repository at this point in the history
…client_endpoints

fix: CQDG-793 update for cavatica
  • Loading branch information
adipaul1981 authored Jul 4, 2024
2 parents f780a35 + 2d96dd2 commit f9bbb10
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 108 deletions.
3 changes: 2 additions & 1 deletion src/main/scala/bio/ferlab/ferload/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ case class DrsConfig(
selfHost: String,
organizationName: String,
organizationUrl: String,
accessId: String,
description: Option[String] = None,
contactUrl: Option[String] = None,
documentationUrl: Option[String] = None,
Expand All @@ -50,11 +51,11 @@ object DrsConfig {
sys.env("DRS_SELF_HOST"),
sys.env("DRS_ORGANIZATION_NAME"),
sys.env("DRS_ORGANIZATION_URL"),
sys.env("DRS_ACCESS_ID"),
sys.env.get("DRS_DESCRIPTION"),
sys.env.get("DRS_CONTACT_URL"),
sys.env.get("DRS_DOCUMENTATION_URL"),
sys.env.get("DRS_ENVIRONMENT"),

)
}
}
Expand Down
43 changes: 35 additions & 8 deletions src/main/scala/bio/ferlab/ferload/endpoints/DrsEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import sttp.tapir.server.ServerEndpoint

object DrsEndpoints:
val baseEndpoint: Endpoint[Unit, Unit, Unit, Unit, Any] = endpoint
.prependSecurityIn("ga4gh")
.prependSecurityIn("drs")
.prependSecurityIn("v1")
.prependSecurityIn("drs")
.prependSecurityIn("ga4gh")

private val service = baseEndpoint.get
.in("service-info")
Expand Down Expand Up @@ -50,15 +50,26 @@ object DrsEndpoints:
.errorOut(statusCode.and(jsonBody[ErrorResponse]))
.out(jsonBody[Authorizations])

private def getObject(authorizationService: AuthorizationService) =
private def getObject(authorizationService: AuthorizationService, method: String) =
objectEnpoint
.securityIn(auth.bearer[String]())
.securityIn(path[String].name("object_id"))
.errorOut(statusCode.and(jsonBody[ErrorResponse]))
.serverSecurityLogic((token, objectId) => authorizationService.authLogic(token, Seq(objectId)))
.serverSecurityLogic((token, objectId) => authorizationService.authLogic(token, Seq(objectId), method))
.get
.out(jsonBody[DrsObject])


private def getAccessMethod(authorizationService: AuthorizationService, method: String) =
objectEnpoint
.securityIn(auth.bearer[String]())
.securityIn(path[String].name("object_id"))
.securityIn("access" / path[String].name("access_id"))
.errorOut(statusCode.and(jsonBody[ErrorResponse]))
.serverSecurityLogic((token, objectId, accessId) => authorizationService.authLogic(token, Seq(objectId), method, Some(accessId)))
.get
.out(jsonBody[AccessURL])

private val createObject: Endpoint[Unit, (String, CreateDrsObject), (StatusCode, ErrorResponse), StatusCode, Any] =
baseEndpoint
.in("object")
Expand All @@ -78,14 +89,29 @@ object DrsEndpoints:
}
}

private def getObjectServer(config: Config, authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service) = getObject(authorizationService).serverLogicSuccess { user =>
private def getObjectServer(config: Config, authorizationService: AuthorizationService, resourceService: ResourceService) = getObject(authorizationService, config.ferloadClientConfig.method).serverLogicSuccess { (user, _) =>
_ =>
for {
resource <- resourceService.getResourceById(user.permissions.head.rsid)
// For now, we only have a unique accessId (all resources are in CEPH S3)
} yield DrsObject.build(resource, config.drsConfig.accessId, config.drsConfig.selfHost)
}


private def getAccessMethodServer(config: Config, authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service) = getAccessMethod(authorizationService, config.ferloadClientConfig.method).serverLogicSuccess { (user, accessId) =>
_ =>
//fetch according to accessId, it is unique for now
if(accessId.isEmpty || config.drsConfig.accessId != accessId.get){
throw HttpError(s"Access Id not found: $accessId", StatusCode.NotFound)
}

for {
resource <- resourceService.getResourceById(user.permissions.head.resource_id)
resource <- resourceService.getResourceById(user.permissions.head.rsid)
bucketAndPath <- IO.fromTry(S3Service.parseS3Urls(resource.uris))
(bucket, path) = bucketAndPath
url = s3Service.presignedUrl(bucket, path)
} yield DrsObject.build(resource, url, config.drsConfig.selfHost)
} yield
AccessURL.build(url)
}

private def createObjectServer(config: Config, resourceService: ResourceService) = createObject.serverLogicSuccess { (token, createDrsObject) =>
Expand All @@ -102,6 +128,7 @@ object DrsEndpoints:
def all(config: Config, authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service): Seq[ServerEndpoint[Any, IO]] = Seq(
serviceServer(config.drsConfig),
objectInfoServer(config, resourceService),
getObjectServer(config, authorizationService, resourceService, s3Service),
getObjectServer(config, authorizationService, resourceService),
getAccessMethodServer(config, authorizationService, resourceService, s3Service),
createObjectServer(config, resourceService)
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ import sttp.tapir.server.*
object LegacyObjectEndpoints:


private def securedGlobalEndpoint(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, Unit, (StatusCode, ErrorResponse), Unit, Any, IO] =
private def securedGlobalEndpoint(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), Unit, (StatusCode, ErrorResponse), Unit, Any, IO] =
endpoint
.securityIn(auth.bearer[String]())
.errorOut(statusCode.and(jsonBody[ErrorResponse]))
.serverSecurityLogic(token => authorizationService.authLogic(token, Seq(resourceGlobalName)))
.serverSecurityLogic(token => authorizationService.authLogic(token, Seq(resourceGlobalName), method: String))

private def objectByPath(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, List[String], (StatusCode, ErrorResponse), ObjectUrl, Any, IO] =
securedGlobalEndpoint(authorizationService, resourceGlobalName)
private def objectByPath(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), List[String], (StatusCode, ErrorResponse), ObjectUrl, Any, IO] =
securedGlobalEndpoint(authorizationService, resourceGlobalName, method)
.get
.description("Retrieve an object by its path and return an url to download it")
.deprecated()
.in(paths.description("Path of the object to retrieve"))
.out(jsonBody[ObjectUrl])

private def objectsByPaths(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, String, (StatusCode, ErrorResponse), Map[String, String], Any, IO] =
securedGlobalEndpoint(authorizationService, resourceGlobalName)
private def objectsByPaths(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), String, (StatusCode, ErrorResponse), Map[String, String], Any, IO] =
securedGlobalEndpoint(authorizationService, resourceGlobalName, method)
.description("Retrieve a list of objects by their paths and return a list of download URLs for each object")
.deprecated()
.post
Expand All @@ -41,13 +41,13 @@ object LegacyObjectEndpoints:
.example(Map("file1.vcf" -> "https://file1.vcf", "file2.vcf" -> "https://file2.vcf"))
)

def objectByPathServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String): ServerEndpoint[Any, IO] =
objectByPath(authorizationService, resourceGlobalName).serverLogicSuccess { user =>
def objectByPathServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String, method: String): ServerEndpoint[Any, IO] =
objectByPath(authorizationService, resourceGlobalName, method).serverLogicSuccess { user =>
file => s3Service.presignedUrl(defaultBucket, file.mkString("/")).pure[IO].map(ObjectUrl.apply)
}

def listObjectsByPathServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String): ServerEndpoint[Any, IO] =
objectsByPaths(authorizationService, resourceGlobalName).serverLogicSuccess { user =>
def listObjectsByPathServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String, method: String): ServerEndpoint[Any, IO] =
objectsByPaths(authorizationService, resourceGlobalName, method).serverLogicSuccess { user =>
files =>
files.split("\n")
.toList
Expand All @@ -59,8 +59,8 @@ object LegacyObjectEndpoints:
b <- config.s3Config.defaultBucket
r <- config.auth.resourcesGlobalName
servers = List(
listObjectsByPathServer(authorizationService, s3Service, r, b),
objectByPathServer(authorizationService, s3Service, r, b)
listObjectsByPathServer(authorizationService, s3Service, r, b, config.ferloadClientConfig.method),
objectByPathServer(authorizationService, s3Service, r, b, config.ferloadClientConfig.method)
)
} yield servers
s.getOrElse(Nil)
Expand Down
48 changes: 24 additions & 24 deletions src/main/scala/bio/ferlab/ferload/endpoints/ObjectsEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,29 @@ object ObjectsEndpoints:

private val byIdEndpoint = baseEndpoint.securityIn("objects")

private def singleObject(authorizationService: AuthorizationService): PartialServerEndpoint[(String, String), User, Unit, (StatusCode, ErrorResponse), ObjectUrl, Any, IO] = byIdEndpoint
private def singleObject(authorizationService: AuthorizationService, method: String): PartialServerEndpoint[(String, String), (User, Option[String]), Unit, (StatusCode, ErrorResponse), ObjectUrl, Any, IO] = byIdEndpoint
.get
.securityIn(path[String].name("object_id"))
.serverSecurityLogic((token, objectId) => authorizationService.authLogic(token, Seq(objectId)))
.serverSecurityLogic((token, objectId) => authorizationService.authLogic(token, Seq(objectId), method))
.description("Retrieve an object by its id and return an url to download it")
.out(jsonBody[ObjectUrl])

private def listObjects(authorizationService: AuthorizationService): PartialServerEndpoint[(String, String), User, Unit, (StatusCode, ErrorResponse), Map[String, String], Any, IO] = byIdEndpoint
private def listObjects(authorizationService: AuthorizationService, method: String): PartialServerEndpoint[(String, String), (User, Option[String]), Unit, (StatusCode, ErrorResponse), Map[String, String], Any, IO] = byIdEndpoint
.post
.securityIn("list")
.securityIn(stringBody.description("List of ids of objects to retrieve").example("FI1\nFI2"))
.serverSecurityLogic((token, objects) => authorizationService.authLogic(token, objects.split("\n")))
.serverSecurityLogic((token, objects) => authorizationService.authLogic(token, objects.split("\n"), method))
.description("Retrieve an object by its id and return an url to download it")
.out(jsonBody[Map[String, String]]
.description("List of files URLs by object id")
.example(Map("FI1" -> "https://file1.vcf", "FI2" -> "https://file2.vcf")))


def singleObjectServer(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service): ServerEndpoint[Any, IO] =
singleObject(authorizationService).serverLogicSuccess { user =>
def singleObjectServer(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service, method: String): ServerEndpoint[Any, IO] =
singleObject(authorizationService, method).serverLogicSuccess { (user, _) =>
_ =>
for {
resource <- resourceService.getResourceById(user.permissions.head.resource_id)
resource <- resourceService.getResourceById(user.permissions.head.rsid)
bucketAndPath <- IO.fromTry(S3Service.parseS3Urls(resource.uris))
(bucket, path) = bucketAndPath
url = s3Service.presignedUrl(bucket, path)
Expand All @@ -51,10 +51,10 @@ object ObjectsEndpoints:
}


def listObjectsServer(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service): ServerEndpoint[Any, IO] =
listObjects(authorizationService).serverLogicSuccess { user =>
def listObjectsServer(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service, method: String): ServerEndpoint[Any, IO] =
listObjects(authorizationService, method).serverLogicSuccess { (user, _) =>
_ =>
val resourcesIO: IO[List[ReadResource]] = user.permissions.toList.traverse(p => resourceService.getResourceById(p.resource_id))
val resourcesIO: IO[List[ReadResource]] = user.permissions.toList.traverse(p => resourceService.getResourceById(p.rsid))
resourcesIO.map { resources =>
val urls: Seq[(String, (String, String))] = resources.flatMap(r => S3Service.parseS3Urls(r.uris).toOption.map(r.name -> _))
val m: Map[String, String] = urls.map { case (name, (bucket, path)) => name -> s3Service.presignedUrl(bucket, path) }.toMap
Expand All @@ -64,31 +64,31 @@ object ObjectsEndpoints:

}

def all(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service): Seq[ServerEndpoint[Any, IO]] = List(
singleObjectServer(authorizationService, resourceService, s3Service),
listObjectsServer(authorizationService, resourceService, s3Service)
def all(authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service, method: String): Seq[ServerEndpoint[Any, IO]] = List(
singleObjectServer(authorizationService, resourceService, s3Service, method),
listObjectsServer(authorizationService, resourceService, s3Service, method)
)

object ByPath:
private def byPathEndpoint(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, Unit, (StatusCode, ErrorResponse), Unit, Any, IO] =
private def byPathEndpoint(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), Unit, (StatusCode, ErrorResponse), Unit, Any, IO] =
baseEndpoint
.securityIn("objects")
.securityIn("bypath")
.serverSecurityLogic(token => authorizationService.authLogic(token, Seq(resourceGlobalName)))
.serverSecurityLogic(token => authorizationService.authLogic(token, Seq(resourceGlobalName), method))

private def singleObject(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, String, (StatusCode, ErrorResponse), ObjectUrl, Any, IO] =
byPathEndpoint(authorizationService, resourceGlobalName)
private def singleObject(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), String, (StatusCode, ErrorResponse), ObjectUrl, Any, IO] =
byPathEndpoint(authorizationService, resourceGlobalName, method)
.get
.description("Retrieve an object by its path and return an url to download it")
.in(query[String]("path").description("Path of the object to retrieve").example("dir1/file1.vcf"))
.out(jsonBody[ObjectUrl])

def singleObjectServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String): ServerEndpoint[Any, IO] =
singleObject(authorizationService, resourceGlobalName).serverLogicSuccess { user =>
def singleObjectServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String, method: String): ServerEndpoint[Any, IO] =
singleObject(authorizationService, resourceGlobalName, method).serverLogicSuccess { user =>
file => s3Service.presignedUrl(defaultBucket, file).pure[IO].map(ObjectUrl.apply)
}

private def listObjects(authorizationService: AuthorizationService, resourceGlobalName: String): PartialServerEndpoint[String, User, String, (StatusCode, ErrorResponse), Map[String, String], Any, IO] = byPathEndpoint(authorizationService, resourceGlobalName)
private def listObjects(authorizationService: AuthorizationService, resourceGlobalName: String, method: String): PartialServerEndpoint[String, (User, Option[String]), String, (StatusCode, ErrorResponse), Map[String, String], Any, IO] = byPathEndpoint(authorizationService, resourceGlobalName, method)
.description("Retrieve a list of objects by their path and return a list of download URLs for each object")
.post
.in("list")
Expand All @@ -98,7 +98,7 @@ object ObjectsEndpoints:
.example(Map("dir1/file1.vcf" -> "https://file1.vcf", "dir1/file2.vcf" -> "https://file2.vcf"))
)

def listObjectsServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String): ServerEndpoint[Any, IO] = listObjects(authorizationService, resourceGlobalName).serverLogicSuccess { user =>
def listObjectsServer(authorizationService: AuthorizationService, s3Service: S3Service, resourceGlobalName: String, defaultBucket: String, method: String): ServerEndpoint[Any, IO] = listObjects(authorizationService, resourceGlobalName, method).serverLogicSuccess { user =>
files =>
files.split("\n")
.toList
Expand All @@ -110,14 +110,14 @@ object ObjectsEndpoints:
b <- config.s3Config.defaultBucket
r <- config.auth.resourcesGlobalName
servers = List(
singleObjectServer(authorizationService, s3Service, r, b),
listObjectsServer(authorizationService, s3Service, r, b)
singleObjectServer(authorizationService, s3Service, r, b, config.ferloadClientConfig.method),
listObjectsServer(authorizationService, s3Service, r, b, config.ferloadClientConfig.method)
)
} yield servers
s.getOrElse(Nil)

}

def all(config: Config, authorizationService: AuthorizationService, resourceService: ResourceService, s3Service: S3Service): Seq[ServerEndpoint[Any, IO]] =
ByPath.all(config, authorizationService, s3Service) ++ ById.all(authorizationService, resourceService, s3Service)
ByPath.all(config, authorizationService, s3Service) ++ ById.all(authorizationService, resourceService, s3Service, config.ferloadClientConfig.method)

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object PermissionsEndpoints:
private def listPermissionsServer(authorizationService: AuthorizationService): ServerEndpoint[Any, IO] =
listPermissions(authorizationService).serverLogicSuccess { user =>
_ =>
IO(user.permissions.map(_.resource_id).toList)
IO(user.permissions.map(_.rsid).toList)
}

def all(authorizationService: AuthorizationService): Seq[ServerEndpoint[Any, IO]] = List(
Expand Down
15 changes: 14 additions & 1 deletion src/main/scala/bio/ferlab/ferload/model/IntrospectResponse.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
package bio.ferlab.ferload.model

case class IntrospectResponse(active: Boolean, exp: Option[Int], iat: Option[Int], aud: Option[String], nbf: Option[Int], permissions: Option[Seq[Permissions]])
case class IntrospectResponse(
active: Boolean,
exp: Option[Int],
iat: Option[Int],
aud: Option[String],
sub: Option[String],
azp: Option[String],
nbf: Option[Int],
authorization: Option[Authorisation]
)

case class Authorisation(
permissions: Seq[Permissions]
)
2 changes: 1 addition & 1 deletion src/main/scala/bio/ferlab/ferload/model/Permissions.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package bio.ferlab.ferload.model

case class Permissions(resource_id: String, rsname: Option[String], resource_scopes: Seq[String])
case class Permissions(rsid: String, rsname: Option[String], scopes: Seq[String])
Loading

0 comments on commit f9bbb10

Please sign in to comment.