backend/app/controllers/api/Blobs.scala (117 lines of code) (raw):
package controllers.api
import commands.DeleteResource
import model.Uri
import model.user.UserPermission.CanPerformAdminOperations
import net.logstash.logback.marker.LogstashMarker
import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent, Request, Result}
import services.ObjectStorage
import services.index.Index
import services.manifest.Manifest
import services.observability.PostgresClient
import utils.Logging
import utils.attempt.{Attempt, DeleteNotAllowed, MissingPermissionFailure}
import utils.auth.User
import utils.controller.{AuthApiController, AuthControllerComponents, FailureToResultMapper}
class Blobs(override val controllerComponents: AuthControllerComponents, manifest: Manifest, index: Index,
objectStorage: ObjectStorage, previewStorage: ObjectStorage, postgresClient: PostgresClient)
extends AuthApiController with Logging {
def param(name: String, req: Request[AnyContent]): Option[String] = req.queryString.get(name).flatMap(_.headOption)
// inMultiple means only return blobs that are also in other collections/ingestions than those supplied.
// For instance:
// ?collection=c&inMultiple=true
// returns blobs in collection c and at least one other collection
//
// ?collection=c&ingestion=i&inMultiple=true
// returns blobs in ingestion c/i and at least one other ingestion
def getBlobs(collection: Option[String], ingestion: Option[String], inMultiple: Option[Boolean], size: Option[Int]) = ApiAction.attempt { req =>
checkPermission(CanPerformAdminOperations, req) {
(collection, ingestion, inMultiple) match {
case (Some(collection), maybeIngestion, maybeInMultiple) =>
index.getBlobs(collection, maybeIngestion, size.getOrElse(500), maybeInMultiple.getOrElse(false)).map(blobs =>
Ok(Json.obj("blobs" -> blobs))
)
case _ =>
Attempt.Right(BadRequest("Missing collection query parameter"))
}
}
}
def countBlobs(collection: Option[String], ingestion: Option[String], inMultiple: Option[Boolean]) = ApiAction.attempt { req =>
checkPermission(CanPerformAdminOperations, req) {
(collection, ingestion, inMultiple) match {
case (Some(collection), maybeIngestion, maybeInMultiple) =>
index.countBlobs(collection, maybeIngestion, maybeInMultiple.getOrElse(false)).map(count =>
Ok(Json.obj("count" -> count))
)
case _ =>
Attempt.Right(BadRequest("Missing collection query parameter"))
}
}
}
def reprocess(id: String, rerunSuccessfulParam: Option[Boolean], rerunFailedParam: Option[Boolean]) = ApiAction.attempt { req =>
userHasViewAccessToBlob(id, req.user.username).flatMap { hasAccess =>
if (hasAccess) {
val uri = Uri(id)
def rerunFailedIfRequested() = {
if (rerunFailedParam.getOrElse(true)) {
logger.info(s"Reprocessing failed extractors for blob ${id}")
manifest.rerunFailedExtractorsForBlob(uri)
} else {
Attempt.Right(())
}
}
def rerunSuccessfulIfRequested() = {
if (rerunSuccessfulParam.getOrElse(true)) {
logger.info(s"Reprocessing successful extractors for blob ${id}")
manifest.rerunSuccessfulExtractorsForBlob(uri)
} else {
Attempt.Right(())
}
}
for {
_ <- rerunFailedIfRequested()
_ <- rerunSuccessfulIfRequested()
} yield {
NoContent
}
} else {
logAction(req.user, s"Can't reprocess resource due to lack of permission. Resource uri: $id")
Attempt.Left[Result](MissingPermissionFailure("Failed to reprocess resource"))
}
}
}
def delete(id: String, checkChildren: Boolean, isAdminDelete: Boolean): Action[AnyContent] = ApiAction.attempt { req =>
import scala.language.existentials
val deleteResource = new DeleteResource(manifest, index, previewStorage, objectStorage, postgresClient)
if (isAdminDelete) {
checkPermission(CanPerformAdminOperations, req) {
val result = if (checkChildren) deleteResource.deleteBlobCheckChildren(id)
else deleteResource.deleteBlob(id)
result.map(_ => NoContent)
}
} else {
deleteForNonAdmin(req.user, id).map(_ => NoContent)
}
}
private def deleteForNonAdmin(user: User, blobUri: String): Attempt[Unit] = {
manifest.getCollectionsForBlob(blobUri).flatMap { collections =>
// Here we check either of 2 followings:
// if there's only 1 collection holding the blob and if the requesting user has view access to the collection
// OR if the requesting user is the only creator of the blob e.g. if a user has uploaded the same file to multiple workspaces
if ((collections.size == 1 && collections.head._2.contains(user.username)) || collections.forall(c => c._1.createdBy == Some(user.username))) {
logAction(user, s"Deleting resource from Giant if no children. Resource uri: $blobUri")
val deleteResource = new DeleteResource(manifest, index, previewStorage, objectStorage, postgresClient)
deleteResource.deleteBlobCheckChildren(blobUri)
} else {
logAction(user, s"Can't delete resource due to file ownership conflict. Resource uri: $blobUri")
Attempt.Left[Unit](DeleteNotAllowed("Failed to delete resource"))
}
}
}
private def userHasViewAccessToBlob(id: String, username: String): Attempt[Boolean] = {
manifest.getCollectionsForBlob(id).flatMap { collections =>
if (collections.exists(c => c._2.contains(username))) {
Attempt.Right(true)
}
else {
manifest.getWorkspacesForBlob(id).map { workspaces =>
workspaces.exists(w => w.followers.exists(u => u.username == username))
}
}
}
}
private def logAction(user: User, message: String) = {
val markers: LogstashMarker = user.asLogMarker
logger.info(markers, message)
}
}