tanishqgandhi1908 commented on code in PR #7922:
URL: https://github.com/apache/texera/pull/7922#discussion_r3859490956
##########
file-service/src/main/scala/org/apache/texera/service/resource/ModelResource.scala:
##########
@@ -408,4 +474,513 @@ class ModelResource extends LazyLogging {
): DashboardModel = {
withTransaction(context)(ctx => getDashboardModel(ctx, mid, None))
}
+
+ @GET
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/{mid}/versionZip")
+ def getModelVersionZip(
+ @PathParam("mid") mid: Integer,
+ @QueryParam("mvid") mvid: Integer,
+ @QueryParam("latest") latest: java.lang.Boolean,
+ @Auth user: SessionUser
+ ): Response =
+ withTransaction(context) { ctx =>
+ if ((mvid != null && latest != null) || (mvid == null && latest ==
null)) {
+ throw new BadRequestException("Specify exactly one: mvid=<ID> OR
latest=true")
+ }
+
+ val uid = user.getUid
+ if (!userHasReadAccess(ctx, mid, uid)) {
+ throw new ForbiddenException(ERR_USER_HAS_NO_ACCESS_TO_MODEL_MESSAGE)
+ }
+
+ val model = getModelByID(ctx, mid)
+ // Non-owners may download only while the owner leaves the model
downloadable.
+ if (!userOwnModel(ctx, mid, uid) && !model.getIsDownloadable) {
+ throw new ForbiddenException("Model download is not allowed")
+ }
+
+ val modelVersion =
+ if (mvid != null) getModelVersionByID(ctx, mvid)
+ else
+ getLatestModelVersion(ctx, mid).getOrElse(
+ throw new NotFoundException(ERR_MODEL_VERSION_NOT_FOUND_MESSAGE)
+ )
+
+ ResourceUploadService.versionZipResponse(
+ model.getRepositoryName,
+ modelVersion.getVersionHash,
+ model.getName,
+ modelVersion.getName
+ )
+ }
+
+ /** Owner facet for the model list page. */
+ @GET
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/user-model-owners")
+ def retrieveOwners(@Auth user: SessionUser): java.util.List[String] =
+ withTransaction(context)(ctx =>
+ ResourceAccess.ownerEmailsVisibleTo(ctx, MODEL_RESOURCE, user.getUid)
+ )
+
+ //
===========================================================================
+ // Staged changes
+ //
===========================================================================
+
+ @GET
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/{mid}/diff")
+ def getModelDiff(
+ @PathParam("mid") mid: Integer,
+ @Auth user: SessionUser
+ ): List[Diff] =
+ ResourceUploadService.stagedChanges(ResourceStorage.Model, mid,
user.getUid)
+
+ @PUT
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/{mid}/diff")
+ def resetModelFileDiff(
+ @PathParam("mid") mid: Integer,
+ @QueryParam("filePath") encodedFilePath: String,
+ @Auth user: SessionUser
+ ): Response =
+ ResourceUploadService.resetStagedChange(
+ ResourceStorage.Model,
+ mid,
+ encodedFilePath,
+ user.getUid
+ )
+
+ @POST
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/{mid}/existing-upload-files")
+ @Consumes(Array(MediaType.APPLICATION_JSON))
+ def findExistingUploadFiles(
+ @PathParam("mid") mid: Integer,
+ request: ExistingUploadFilesRequest,
+ @Auth user: SessionUser
+ ): Response =
+ ResourceUploadService.matchExistingUploads(
+ ResourceStorage.Model,
+ mid,
+ user.getUid,
+ request,
+ ctx => getLatestModelVersion(ctx, mid).map(_.getVersionHash)
+ )
+
+ //
===========================================================================
+ // Presigned downloads
+ //
===========================================================================
+
+ /**
+ * Resolves a presign request against the model tables and wraps the signed
URL.
+ * The resolution itself is shared with datasets; only the descriptor
differs.
+ */
+ /** Size of a model's LakeFS repository, or 0 if LakeFS cannot answer. */
+ private def repositorySizeOrZero(model: Model): Long = {
+ try {
+ LakeFSStorageClient.retrieveRepositorySize(model.getRepositoryName)
+ } catch {
+ case e: io.lakefs.clients.sdk.ApiException =>
+ logger.error(
+ s"LakeFS ApiException for model repository
'${model.getRepositoryName}': ${e.getMessage}",
+ e
+ )
+ 0L
+ }
+ }
+
+ private def generatePresignedResponse(
+ encodedUrl: String,
+ repositoryName: String,
+ commitHash: String,
+ uid: Integer
+ ): Response =
+ ResourceUploadService.presignedUrlResponse(
+ ResourceStorage.Model,
+ encodedUrl,
+ repositoryName,
+ commitHash,
+ uid
+ )
+
+ @GET
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/presign-download")
+ def getPresignedUrl(
+ @QueryParam("filePath") encodedUrl: String,
+ @QueryParam("repositoryName") repositoryName: String,
+ @QueryParam("commitHash") commitHash: String,
+ @Auth user: SessionUser
+ ): Response =
+ generatePresignedResponse(encodedUrl, repositoryName, commitHash,
user.getUid)
+
+ @GET
+ @RolesAllowed(Array("REGULAR", "ADMIN"))
+ @Path("/presign-download-s3")
+ def getPresignedUrlWithS3(
+ @QueryParam("filePath") encodedUrl: String,
+ @QueryParam("repositoryName") repositoryName: String,
+ @QueryParam("commitHash") commitHash: String,
+ @Auth user: SessionUser
+ ): Response =
+ generatePresignedResponse(encodedUrl, repositoryName, commitHash,
user.getUid)
+
+ @GET
+ @PermitAll
+ @Path("/public-presign-download")
+ def getPublicPresignedUrl(
Review Comment:
Good catch — fixed. is_downloadable now rides on ResourceTables and is
checked in requireReadAccessToRepository, so the presign routes enforce the
same gate as getModelVersionZip. Both routes you described now 403.
Datasets have the same gap but I've left the gate off there: their presign
also backs file preview, so turning it on would stop non-owners previewing a
public non-downloadable dataset — a behaviour change to a shipped feature. The
descriptor is wired, so it's a one-line switch whenever we decide that. Happy
to do it here if you'd prefer.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]