backend/app/utils/controller/DownloadHelper.scala (83 lines of code) (raw):

package utils.controller import java.net.URLEncoder import java.nio.charset.StandardCharsets import commands.{GetResource, ResourceFetchMode} import model.Uri import play.api.mvc._ import play.utils.UriEncoding import services.annotations.Annotations import services.index.Index import utils.Logging import utils.attempt.{Attempt, AuthenticationFailure} import scala.concurrent.ExecutionContext import scala.concurrent.duration.FiniteDuration import scala.util.Try import services.manifest.Manifest import services.users.UserManagement import utils.auth.UserIdentityRequest trait DownloadHelper extends Logging { def checkResource(username: String, url: String)(implicit ec: ExecutionContext): Attempt[Unit] def downloadExpiryPeriod: FiniteDuration private def downloadSessionKeyPrefix: String = "dwnldAuth:" protected def makeSessionKey(target: String) = s"$downloadSessionKeyPrefix$target" def AuthoriseDownload(resourceUri: Uri, target: Call)(implicit request: UserIdentityRequest[_], ec: ExecutionContext): Attempt[Result] = { val filename = request.queryString.get("filename").map(_.head) val now = System.currentTimeMillis() checkResource(request.user.username, resourceUri.value).map { _ => logger.info(s"Authorised download of '${resourceUri.value}' as '${filename}' via ${target.url} by ${request.user.username}") authoriseDownload(target.url, filename, now, request.session) }.recoverWith { case err => logger.warn(s"Disallowed download of '${resourceUri.value}' as '${filename}' via ${target.url} by ${request.user.username}: $err") Attempt.Left(err) } } def authoriseDownload(target: String, filename: Option[String], now: Long, session: Session): Result = { val fullTarget = filename.map(target + "?filename=" + URLEncoder.encode(_, "UTF-8")).getOrElse(target) val sessionKey = makeSessionKey(fullTarget) val exp = now + downloadExpiryPeriod.toMillis val newSession = session + (sessionKey -> exp.toString) Results.Ok(fullTarget).withSession(newSession) } def authorisedToDownload(block: => Attempt[Result]) (implicit request: RequestHeader, ec: ExecutionContext): Attempt[Result] = { val (authResult, newSession) = authorisedToDownload(request.uri, request.session, System.currentTimeMillis()) val failureOrResult = authResult match { case Some(true) => block case Some(false) => Attempt.Left(AuthenticationFailure("Download session key expired or missing", reportAsFailure = true)) case None => Attempt.Left(AuthenticationFailure("Download session key missing", reportAsFailure = true)) } failureOrResult.map(_.withSession(newSession)) } def authorisedToDownload(uri: String, session: Session, now: Long): (Option[Boolean], Session) = { val sessionKey = makeSessionKey(uri) logger.info(s"Reconstructed session key from URL: $sessionKey") val result = session.get(sessionKey).flatMap(exp => Try(exp.toLong).toOption) match { case Some(exp) if exp > now => Some(true) case Some(exp) => Some(false) case _ => None } val newSession = removeExpiredDownloadKeys(session, now) (result, newSession) } private def removeExpiredDownloadKeys(session: Session, now: Long): Session = Session(session.data.filterNot { case (key, value) => key.startsWith(downloadSessionKeyPrefix) && Try(value.toLong).toOption.exists(_ < now) }) } trait ResourceDownloadHelper extends DownloadHelper { def manifest: Manifest def index: Index def annotations: Annotations def users: UserManagement final override def checkResource(username: String, url: String)(implicit ec: ExecutionContext): Attempt[Unit] = { val decodedUri = Uri(UriEncoding.decodePath(url, StandardCharsets.UTF_8)) GetResource(decodedUri, ResourceFetchMode.Basic, username, manifest, index, annotations, users).process().map(_ => ()) } }